@51jbs/incremental-coverage-plugin 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/index.d.mts +314 -0
- package/dist/index.d.ts +314 -0
- package/dist/index.js +1399 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1364 -0
- package/dist/index.mjs.map +1 -0
- package/dist/types-PPxtKDSr.d.mts +57 -0
- package/dist/types-PPxtKDSr.d.ts +57 -0
- package/dist/vite.d.mts +26 -0
- package/dist/vite.d.ts +26 -0
- package/dist/vite.js +1390 -0
- package/dist/vite.js.map +1 -0
- package/dist/vite.mjs +1360 -0
- package/dist/vite.mjs.map +1 -0
- package/dist/webpack.d.mts +26 -0
- package/dist/webpack.d.ts +26 -0
- package/dist/webpack.js +1390 -0
- package/dist/webpack.js.map +1 -0
- package/dist/webpack.mjs +1360 -0
- package/dist/webpack.mjs.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/plugin.ts","../src/collector.ts","../src/differ.ts","../src/git.ts","../src/reporter.ts"],"sourcesContent":["/**\n * 主入口文件\n * \n * 导出插件的所有公共 API\n * \n * 使用方式:\n * ```typescript\n * import { IncrementalCoveragePlugin } from '@51jbs/incremental-coverage-plugin';\n * ```\n * \n * @module index\n */\n\n// 导出主插件\nexport { IncrementalCoveragePlugin } from './plugin';\n\n// 导出类型定义\nexport type { IncrementalCoverageOptions } from './types';\n\n// 导出核心模块(供高级用户使用)\nexport { CoverageCollector } from './collector';\nexport { CoverageDiffer } from './differ';\nexport { CoverageReporter } from './reporter';\n\n// 导出 Git 工具函数\nexport { getGitDiff, getChangedFiles } from './git';\n","/**\n * 主插件文件\n * \n * 使用 unplugin 创建统一的插件接口,支持 Webpack、Vite 等多种构建工具\n * 负责初始化各个模块并协调它们之间的交互\n * \n * @module plugin\n */\n\nimport { createUnplugin } from 'unplugin';\nimport type { IncrementalCoverageOptions } from './types';\nimport { CoverageCollector } from './collector';\nimport { CoverageDiffer } from './differ';\nimport { CoverageReporter } from './reporter';\n\n/**\n * 默认配置选项\n * \n * 提供合理的默认值,用户可以根据需要覆盖这些配置\n */\nconst defaultOptions: IncrementalCoverageOptions = {\n // 默认只包含 src 目录下的文件\n include: ['src/**'],\n\n // 默认排除测试文件和 node_modules\n exclude: ['**/*.spec.ts', '**/*.test.ts', '**/node_modules/**'],\n\n // 默认与 main 分支对比\n gitDiffBase: 'main',\n\n // 默认输出到 .coverage 目录\n outputDir: '.coverage',\n\n // 默认生成 HTML 格式报告\n reportFormat: 'html',\n\n // 默认启用浏览器 UI\n enableOverlay: true,\n\n // 默认 baseline 文件路径\n baselinePath: '.coverage/baseline.json',\n\n // 默认自动保存 baseline\n autoSaveBaseline: true,\n\n // 默认覆盖率阈值 80%\n threshold: 80,\n // 默认报告生成间隔 10秒\n reportInterval: 10000,\n // 默认保留 15 个历史报告\n historyCount: 15,\n};\n\n/**\n * Incremental Coverage Plugin\n * \n * 轻量级增量覆盖率插件,基于以下技术:\n * - babel-plugin-istanbul: 代码插桩\n * - istanbul-diff: 增量覆盖率计算\n * - unplugin: 多构建工具支持\n * \n * @example\n * // Webpack\n * new WebpackIncrementalCoveragePlugin({ include: ['src/**'] })\n * \n * // Vite\n * ViteIncrementalCoveragePlugin({ include: ['src/**'] })\n */\nexport const IncrementalCoveragePlugin = createUnplugin<IncrementalCoverageOptions | undefined>((userOptions = {}) => {\n // 合并用户配置和默认配置\n const options = { ...defaultOptions, ...userOptions };\n\n // 初始化各个模块\n const collector = new CoverageCollector();\n const differ = new CoverageDiffer(options);\n const reporter = new CoverageReporter(options);\n\n // 用于防抖处理\n let lastReportTime = 0;\n let pendingResult: any = null;\n let reportTimer: NodeJS.Timeout | null = null;\n\n console.log('[IncrementalCoverage] 插件已初始化');\n\n return {\n // 插件名称\n name: 'incremental-coverage-plugin',\n\n /**\n * Webpack 特定的钩子\n * \n * 在 Webpack 环境中,需要:\n * 1. 注入 babel-plugin-istanbul 配置\n * 2. 设置 HTTP 中间件接收覆盖率数据\n * 3. 监听编译生命周期\n */\n webpack(compiler) {\n console.log('[IncrementalCoverage] Webpack 模式');\n\n // 1. 注入 babel-plugin-istanbul 配置\n const rules = (compiler.options.module?.rules || []) as any[];\n const processed = new WeakSet();\n\n const injectBabel = (rule: any) => {\n if (!rule || typeof rule !== 'object' || processed.has(rule)) return;\n processed.add(rule);\n\n // 1. 检查 direct loader (对象形式)\n const isBabel = rule.loader && (typeof rule.loader === 'string') && (rule.loader.includes('babel-loader') || rule.loader === 'babel-loader');\n if (isBabel) {\n if (!rule.options) rule.options = {};\n if (typeof rule.options === 'object') {\n rule.options.plugins = rule.options.plugins || [];\n const hasIstanbul = rule.options.plugins.some((p: any) => {\n const name = Array.isArray(p) ? p[0] : p;\n return typeof name === 'string' && name.includes('istanbul');\n });\n if (!hasIstanbul) {\n rule.options.plugins.push([\n 'babel-plugin-istanbul',\n {\n extension: ['.js', '.jsx', '.ts', '.tsx', '.vue'],\n include: options.include,\n exclude: options.exclude || ['**/*.spec.ts', '**/*.test.ts', '**/node_modules/**']\n }\n ]);\n console.log('[IncrementalCoverage] 已注入到 babel-loader');\n }\n }\n }\n\n // 2. 检查 use 数组或对象\n if (rule.use) {\n if (Array.isArray(rule.use)) {\n rule.use.forEach((u: any, index: number) => {\n if (typeof u === 'object') {\n injectBabel(u);\n } else if (typeof u === 'string' && (u.includes('babel-loader') || u === 'babel-loader')) {\n // 将字符串转换为对象\n rule.use[index] = {\n loader: u,\n options: {\n plugins: [[\n 'babel-plugin-istanbul',\n {\n extension: ['.js', '.jsx', '.ts', '.tsx', '.vue'],\n include: options.include,\n exclude: options.exclude || ['**/*.spec.ts', '**/*.test.ts', '**/node_modules/**']\n }\n ]]\n }\n };\n console.log('[IncrementalCoverage] 已将字符串 babel-loader 转换为对象并注入');\n }\n });\n } else if (typeof rule.use === 'object') {\n injectBabel(rule.use);\n } else if (typeof rule.use === 'string' && (rule.use.includes('babel-loader') || rule.use === 'babel-loader')) {\n rule.use = {\n loader: rule.use,\n options: {\n plugins: [[\n 'babel-plugin-istanbul',\n {\n extension: ['.js', '.jsx', '.ts', '.tsx', '.vue'],\n include: options.include,\n exclude: options.exclude || ['**/*.spec.ts', '**/*.test.ts', '**/node_modules/**']\n }\n ]]\n }\n };\n console.log('[IncrementalCoverage] 已将 rule.use 字符串转换为对象并注入');\n }\n }\n\n // 3. 递归 oneOf 等\n if (rule.oneOf && Array.isArray(rule.oneOf)) {\n rule.oneOf.forEach(injectBabel);\n }\n };\n\n rules.forEach(injectBabel);\n\n // 2. 设置中间件接收覆盖率数据\n // 自动检测并兼容 webpack-dev-server 3.x 和 4.x\n if (!compiler.options.devServer) {\n compiler.options.devServer = {};\n }\n\n // 提取通用的覆盖率处理函数\n const createCoverageHandler = () => {\n return async (req: any, res: any) => {\n const startTime = Date.now();\n\n // 定义处理逻辑\n const handleCoverage = async (payload: any) => {\n let coverageData;\n\n // 处理压缩数据\n if (payload.data && req.headers['x-coverage-compressed']) {\n try {\n const compressed = payload.data;\n const decompressed = Buffer.from(compressed, 'base64').toString();\n coverageData = JSON.parse(decodeURIComponent(decompressed));\n\n console.log('[IncrementalCoverage] 数据已解压缩 (压缩率: ' +\n Math.round((1 - compressed.length / JSON.stringify(coverageData).length) * 100) + '%)');\n } catch (e) {\n console.warn('[IncrementalCoverage] 解压失败,回退到原始数据:', e);\n coverageData = payload.data ? payload.data : payload;\n }\n } else {\n coverageData = payload;\n }\n\n // 合并并计算\n const merged = collector.merge(coverageData);\n const result = await differ.calculate(merged);\n pendingResult = result;\n\n // 防抖逻辑\n const now = Date.now();\n const elapsed = now - lastReportTime;\n const interval = options.reportInterval || 10000;\n\n if (elapsed >= interval) {\n await reporter.generate(result);\n lastReportTime = now;\n if (reportTimer) {\n clearTimeout(reportTimer);\n reportTimer = null;\n }\n } else if (!reportTimer) {\n console.log(`[IncrementalCoverage] 报告生成冷却中,将在 ${interval - elapsed}ms 后尝试...`);\n reportTimer = setTimeout(async () => {\n if (pendingResult) {\n await reporter.generate(pendingResult);\n lastReportTime = Date.now();\n }\n reportTimer = null;\n }, interval - elapsed);\n }\n\n res.json({\n status: 'ok',\n coverage: {\n rate: result.overall.coverageRate,\n coveredLines: result.overall.coveredLines,\n totalLines: result.overall.totalLines,\n fileCount: result.files.length\n }\n });\n };\n\n try {\n // 1. 如果 body-parser 已经处理了 body\n if (req.body && typeof req.body === 'object' && Object.keys(req.body).length > 0) {\n await handleCoverage(req.body);\n return;\n }\n\n // 2. 否则,手动收集流数据\n let chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => { chunks.push(chunk); });\n req.on('end', async () => {\n try {\n const body = Buffer.concat(chunks).toString();\n if (!body) {\n res.status(400).json({ success: false, error: 'Empty body' });\n return;\n }\n const payload = JSON.parse(body);\n await handleCoverage(payload);\n } catch (parseError) {\n console.error('[IncrementalCoverage] 接收数据解析失败:', parseError);\n res.status(400).json({ success: false, error: 'Invalid data format' });\n }\n });\n } catch (error) {\n console.error('[IncrementalCoverage] 中间件处理异常:', error);\n res.status(500).json({ success: false, error: String(error) });\n }\n };\n };\n\n const coverageHandler = createCoverageHandler();\n\n // 检测使用哪种 API\n // webpack-dev-server 4.x/5.x 使用 setupMiddlewares\n // webpack-dev-server 3.x 使用 before\n const hasSetupMiddlewares = 'setupMiddlewares' in (compiler.options.devServer || {});\n const hasBefore = 'before' in (compiler.options.devServer || {});\n\n // 尝试通过检测 webpack-dev-server 版本来决定使用哪个 API\n let useSetupMiddlewares = hasSetupMiddlewares;\n\n // 如果两者都未配置,尝试检测 webpack-dev-server 版本\n if (!hasSetupMiddlewares && !hasBefore) {\n try {\n const wdsVersion = require('webpack-dev-server/package.json').version;\n const majorVersion = parseInt(wdsVersion.split('.')[0], 10);\n useSetupMiddlewares = majorVersion >= 4;\n console.log(`[IncrementalCoverage] 检测到 webpack-dev-server v${wdsVersion},使用 ${useSetupMiddlewares ? 'setupMiddlewares' : 'before'} API`);\n } catch (e) {\n // 如果无法检测版本,默认使用 before API (更安全的回退)\n console.warn('[IncrementalCoverage] 无法检测 webpack-dev-server 版本,默认使用 before API');\n useSetupMiddlewares = false;\n }\n }\n\n if (useSetupMiddlewares) {\n // webpack-dev-server 4.x/5.x\n const originalSetupMiddlewares = compiler.options.devServer.setupMiddlewares;\n compiler.options.devServer.setupMiddlewares = (middlewares: any, devServer: any) => {\n if (originalSetupMiddlewares) {\n middlewares = originalSetupMiddlewares(middlewares, devServer);\n }\n\n devServer.app?.post('/coverage', coverageHandler);\n console.log('[IncrementalCoverage] 中间件已注册 (webpack-dev-server 4.x+)');\n return middlewares;\n };\n } else {\n // webpack-dev-server 3.x\n const originalBefore = compiler.options.devServer.before;\n compiler.options.devServer.before = (app: any, server: any, compiler: any) => {\n if (originalBefore) {\n originalBefore(app, server, compiler);\n }\n\n app.post('/coverage', coverageHandler);\n console.log('[IncrementalCoverage] 中间件已注册 (webpack-dev-server 3.x)');\n };\n }\n\n // 3. 监听编译完成,注入客户端脚本\n compiler.hooks.compilation.tap('IncrementalCoveragePlugin', (compilation: any) => {\n // 3.1 生成客户端脚本文件\n // Webpack 4 使用 additionalAssets,Webpack 5+ 使用 processAssets\n const isWebpack4 = !compiler.webpack || !compiler.webpack.Compilation || !compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS;\n\n const emitClientScript = () => {\n // Incremental Coverage Client Script (Hardened + Overlay)\n const clientScript = `\n (function () {\n console.log('[Coverage Client] 启动 v2.2 with Overlay');\n\n var config = {\n endpoint: '/coverage',\n interval: 5000,\n maxRetries: 3,\n retryDelay: 1000,\n timeout: 5000,\n storageKey: '__incremental_coverage_cache__',\n enableOverlay: ${options.enableOverlay !== false}\n };\n\n var state = {\n lastDataHash: '',\n isReporting: false,\n retryQueue: JSON.parse(localStorage.getItem(config.storageKey) || '[]')\n };\n\n // --- Overlay Logic ---\n var overlay = null;\n\n function createOverlay() {\n if (!config.enableOverlay || overlay) return;\n\n var host = document.createElement('div');\n host.id = 'inc-cov-overlay-host';\n host.style.cssText = 'position:fixed;bottom:20px;right:20px;z-index:999999;font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;';\n document.body.appendChild(host);\n\n var shadow = host.attachShadow({ mode: 'open' });\n var container = document.createElement('div');\n container.id = 'inc-cov-pill';\n\n var style = document.createElement('style');\n style.textContent = \\`\n :host { all: initial; }\n .pill {\n background: #222;\n color: #fff;\n padding: 8px 16px;\n border-radius: 99px;\n box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n display: flex;\n align-items: center;\n gap: 10px;\n border: 1px solid rgba(255,255,255,0.1);\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n }\n .pill:hover { \n transform: translateY(-2px); \n box-shadow: 0 8px 20px rgba(0,0,0,0.25);\n background: #2a2a2a;\n }\n .dot { \n width: 8px; \n height: 8px; \n border-radius: 50%; \n background: #666; \n box-shadow: 0 0 8px rgba(255,255,255,0.2);\n transition: background 0.3s ease;\n }\n .content { display: flex; flex-direction: column; line-height: 1.2; }\n .label { font-size: 10px; opacity: 0.6; text-transform: uppercase; letter-spacing: 0.5px; }\n .value { font-size: 14px; font-variant-numeric: tabular-nums; }\n \\`;\n \n shadow.appendChild(style);\n shadow.appendChild(container);\n \n overlay = { shadow: shadow, container: container };\n updateOverlay(null, 'init');\n \n // Click to open report (optional, logic needed to know report URL or open new tab)\n // For now, maybe just log or simple alert\n container.addEventListener('click', function() {\n console.log('[Coverage Overlay] Clicked');\n });\n }\n\n function updateOverlay(data, status) {\n if (!overlay) return;\n var container = overlay.container;\n \n var color = '#666';\n var labelText = 'Ready';\n var valueText = 'Inc. Coverage';\n \n if (status === 'init') {\n valueText = 'Connecting...';\n } else if (status === 'ok' && data) {\n var rate = data.rate;\n if (rate >= 80) color = '#10b981'; // Green\n else if (rate >= 50) color = '#f59e0b'; // Orange\n else color = '#ef4444'; // Red\n \n labelText = 'Changed Lines: ' + data.totalLines;\n valueText = rate + '%';\n } else {\n color = '#ef4444';\n valueText = 'Error';\n }\n\n container.innerHTML = \\`\n <div class=\"pill\">\n <div class=\"dot\" style=\"background: \\${color}; box-shadow: 0 0 8px \\${color}66;\"></div>\n <div class=\"content\">\n <span class=\"label\">\\${labelText}</span>\n <span class=\"value\">\\${valueText}</span>\n </div>\n </div>\n \\`;\n }\n\n function getHash(obj) {\n return JSON.stringify(obj).length + '';\n }\n\n function compressAndEncode(data) {\n try {\n return btoa(encodeURIComponent(JSON.stringify(data)));\n } catch (e) {\n console.error('[Coverage Client] 编码失败:', e);\n return null;\n }\n }\n\n function sendData(coverageData, isRetry) {\n if (state.isReporting && !isRetry) return;\n \n var currentHash = getHash(coverageData);\n if (!isRetry && currentHash === state.lastDataHash) return;\n\n state.isReporting = true;\n var payload = compressAndEncode(coverageData);\n if (!payload) { state.isReporting = false; return; }\n\n fetch(config.endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'X-Coverage-Compressed': 'base64' },\n body: JSON.stringify({ data: payload }),\n signal: AbortSignal.timeout ? AbortSignal.timeout(config.timeout) : undefined\n })\n .then(function(res) {\n return res.json().then(function(json) {\n if (res.ok) { return json; }\n throw new Error(json.error || 'Server error');\n });\n })\n .then(function(json) {\n state.lastDataHash = currentHash;\n \n // Update Overlay\n if (json.coverage) {\n updateOverlay(json.coverage, 'ok');\n }\n \n if (state.retryQueue.length > 0) {\n var next = state.retryQueue.shift();\n localStorage.setItem(config.storageKey, JSON.stringify(state.retryQueue));\n sendData(next, true);\n }\n })\n .catch(function(err) {\n console.warn('[Coverage Client] 上报失败:', err.message);\n updateOverlay(null, 'error');\n if (!isRetry) {\n state.retryQueue.push(coverageData);\n if (state.retryQueue.length > 10) state.retryQueue.shift();\n localStorage.setItem(config.storageKey, JSON.stringify(state.retryQueue));\n }\n })\n .finally(function() {\n state.isReporting = false;\n });\n }\n\n // Init\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', createOverlay);\n } else {\n createOverlay();\n }\n\n setInterval(function() {\n if (window.__coverage__) {\n sendData(window.__coverage__, false);\n }\n }, config.interval);\n\n window.addEventListener('beforeunload', function() {\n if (window.__coverage__ && getHash(window.__coverage__) !== state.lastDataHash) {\n var payload = compressAndEncode(window.__coverage__);\n if (payload) {\n navigator.sendBeacon(config.endpoint, JSON.stringify({ data: payload }));\n }\n }\n });\n})();\n `.trim();\n\n // Webpack 4 和 5 使用不同的 API 来添加资源\n if (isWebpack4) {\n // Webpack 4: 使用 RawSource from webpack-sources\n // 尝试从 webpack 内部获取,如果失败则尝试独立包\n let RawSource;\n try {\n // 先尝试从 webpack 内部获取 (Webpack 4 通常会包含)\n const webpack = require('webpack');\n RawSource = webpack.sources?.RawSource;\n if (!RawSource) {\n // 如果 webpack.sources 不存在,尝试独立包\n ({ RawSource } = require('webpack-sources'));\n }\n } catch (e) {\n // 最后的回退:尝试独立的 webpack-sources 包\n try {\n ({ RawSource } = require('webpack-sources'));\n } catch (e2) {\n console.error('[IncrementalCoverage] 无法加载 webpack-sources:', e2);\n throw new Error('webpack-sources is required for Webpack 4. Please install it: npm install webpack-sources');\n }\n }\n compilation.assets['coverage-client.js'] = new RawSource(clientScript);\n } else {\n // Webpack 5: 使用 compiler.webpack.sources.RawSource\n compilation.emitAsset(\n 'coverage-client.js',\n new compiler.webpack.sources.RawSource(clientScript)\n );\n }\n };\n\n if (isWebpack4) {\n // Webpack 4: 使用 additionalAssets hook\n compilation.hooks.additionalAssets.tap('IncrementalCoveragePlugin', emitClientScript);\n console.log('[IncrementalCoverage] 使用 Webpack 4 API (additionalAssets)');\n } else {\n // Webpack 5+: 使用 processAssets hook\n compilation.hooks.processAssets.tap(\n {\n name: 'IncrementalCoveragePlugin',\n stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,\n },\n emitClientScript\n );\n console.log('[IncrementalCoverage] 使用 Webpack 5+ API (processAssets)');\n }\n\n // 3.2 集成 HtmlWebpackPlugin,自动注入 script 标签\n const HtmlWebpackPlugin = compiler.options.plugins?.find(\n (plugin: any) => plugin.constructor.name === 'HtmlWebpackPlugin'\n )?.constructor;\n\n if (HtmlWebpackPlugin) {\n (HtmlWebpackPlugin as any).getHooks(compilation).alterAssetTagGroups.tapAsync(\n 'IncrementalCoveragePlugin',\n (data: any, cb: any) => {\n // 在 body 结束前注入脚本\n data.bodyTags.push({\n tagName: 'script',\n voidTag: false,\n attributes: {\n src: '/coverage-client.js'\n }\n });\n console.log('[IncrementalCoverage] 客户端脚本已自动注入');\n cb(null, data);\n }\n );\n } else {\n console.warn('[IncrementalCoverage] 未找到 HtmlWebpackPlugin,客户端脚本需要手动引入');\n }\n });\n\n // 4. 优雅退出钩子\n const finalReport = async () => {\n if (pendingResult) {\n console.log('[IncrementalCoverage] 正在生成退出前的最终报告...');\n try {\n await reporter.generate(pendingResult);\n } catch (e) {\n console.error('[IncrementalCoverage] 退出前生成报告失败:', e);\n }\n pendingResult = null;\n }\n };\n\n process.once('SIGINT', async () => {\n await finalReport();\n process.exit(0);\n });\n process.once('SIGTERM', async () => {\n await finalReport();\n process.exit(0);\n });\n },\n\n /**\n * Vite 特定的钩子\n * \n * 在 Vite 环境中,需要:\n * 1. 通过 transform 钩子注入 babel-plugin-istanbul\n * 2. 通过 configureServer 添加中间件\n * 3. 支持 HMR(热模块替换)\n */\n vite: {\n // 只在开发模式下应用\n apply: 'serve',\n\n /**\n * 配置解析完成后的钩子\n * \n * 在这里可以访问完整的 Vite 配置\n */\n configResolved(config) {\n console.log('[IncrementalCoverage] Vite 模式');\n\n // TODO: 注入 babel-plugin-istanbul 配置\n // 需要在 transform 钩子中使用 babel 转换代码\n },\n\n /**\n * 配置开发服务器的钩子\n * \n * 在这里添加自定义中间件\n */\n configureServer(server) {\n // TODO: 设置中间件接收覆盖率数据\n // 添加 POST /coverage 端点\n\n // TODO: 添加 HMR 支持\n // 在热更新时重新计算覆盖率\n },\n },\n\n /**\n * 构建开始时的钩子\n * \n * 可以在这里进行初始化工作\n */\n buildStart() {\n console.log('[IncrementalCoverage] 构建开始');\n },\n\n /**\n * 构建结束时的钩子\n * \n * 可以在这里进行清理工作\n */\n buildEnd() {\n console.log('[IncrementalCoverage] 构建结束');\n },\n };\n});\n\n// 默认导出\nexport default IncrementalCoveragePlugin;\n\n// 导出类型定义\nexport type { IncrementalCoverageOptions };\n","/**\n * 覆盖率收集器\n * \n * 负责接收和合并来自浏览器的覆盖率数据\n * 维护全局的覆盖率状态,支持多次上报的数据累加\n * \n * @module collector\n */\n\nimport * as path from 'path';\nimport type { CoverageMap, CoverageData } from './types';\n\n/**\n * Coverage Collector - 覆盖率收集器\n * \n * 核心职责:\n * 1. 接收浏览器上报的覆盖率数据\n * 2. 合并多次上报的数据(累加执行次数)\n * 3. 维护全局覆盖率状态\n * \n * @example\n * const collector = new CoverageCollector();\n * const merged = collector.merge(newCoverageData);\n */\nexport class CoverageCollector {\n /**\n * 全局覆盖率映射表\n * \n * 键是文件路径,值是该文件的覆盖率数据\n * 这个对象会随着数据上报不断更新\n */\n private coverageMap: CoverageMap = {};\n\n /**\n * 合并新的覆盖率数据\n * \n * 算法说明:\n * 1. 遍历新数据中的每个文件\n * 2. 如果是首次出现的文件,直接存储\n * 3. 如果文件已存在,合并执行次数(累加)\n * \n * 为什么要累加?\n * - 用户可能多次执行同一段代码\n * - 需要记录总的执行次数,而不是覆盖\n * \n * @param newCoverage - 新上报的覆盖率数据\n * @returns 合并后的完整覆盖率数据\n * \n * @example\n * // 第一次上报\n * collector.merge({ 'src/utils.ts': { s: { '0': 1 } } });\n * // 第二次上报(同一文件)\n * collector.merge({ 'src/utils.ts': { s: { '0': 2 } } });\n * // 结果:s['0'] = 3(累加)\n */\n merge(newCoverage: CoverageMap): CoverageMap {\n const projectRoot = process.cwd();\n // 遍历新数据的每个文件\n for (const [key, data] of Object.entries(newCoverage)) {\n // 归一化路径,确保绝对路径/相对路径都能匹配\n const filePath = path.isAbsolute(key) ? key : path.resolve(projectRoot, key);\n\n if (!this.coverageMap[filePath]) {\n // 首次出现的文件,直接存储\n this.coverageMap[filePath] = data;\n } else {\n // 已存在的文件,合并数据\n this.coverageMap[filePath] = this.mergeCoverageData(\n this.coverageMap[filePath],\n data\n );\n }\n }\n\n // 返回合并后的完整数据\n return this.coverageMap;\n }\n\n /**\n * 获取当前的覆盖率数据\n * \n * @returns 当前的完整覆盖率映射表\n */\n getCoverage(): CoverageMap {\n return this.coverageMap;\n }\n\n /**\n * 重置覆盖率数据\n * \n * 清空所有已收集的数据,重新开始\n * 通常在需要重新计算覆盖率时使用\n */\n reset(): void {\n this.coverageMap = {};\n }\n\n /**\n * 合并两个覆盖率数据对象\n * \n * 合并策略:\n * 1. 语句覆盖(s):执行次数累加\n * 2. 函数覆盖(f):执行次数累加\n * 3. 分支覆盖(b):数组元素逐个累加\n * \n * 注意:\n * - statementMap、fnMap、branchMap 不需要合并(它们是静态的位置信息)\n * - 只有执行次数(s、f、b)需要累加\n * \n * @param existing - 已存在的覆盖率数据\n * @param newData - 新的覆盖率数据\n * @returns 合并后的覆盖率数据\n * \n * @private\n */\n private mergeCoverageData(existing: CoverageData, newData: CoverageData): CoverageData {\n // 创建合并后的数据对象\n const merged: CoverageData = {\n path: existing.path,\n // 位置映射表保持不变(它们是静态的)\n statementMap: existing.statementMap,\n fnMap: existing.fnMap,\n branchMap: existing.branchMap,\n // 执行次数需要累加\n s: { ...existing.s },\n f: { ...existing.f },\n b: { ...existing.b },\n };\n\n // 合并语句执行次数\n // 例如:existing.s['0'] = 5, newData.s['0'] = 3\n // 结果:merged.s['0'] = 8\n for (const [key, count] of Object.entries(newData.s)) {\n merged.s[key] = (merged.s[key] || 0) + count;\n }\n\n // 合并函数执行次数\n // 逻辑同上\n for (const [key, count] of Object.entries(newData.f)) {\n merged.f[key] = (merged.f[key] || 0) + count;\n }\n\n // 合并分支执行次数\n // 分支是数组,需要逐个元素累加\n // 例如:existing.b['0'] = [2, 1], newData.b['0'] = [1, 2]\n // 结果:merged.b['0'] = [3, 3]\n for (const [key, counts] of Object.entries(newData.b)) {\n if (!merged.b[key]) {\n // 如果原数据中没有这个分支,直接使用新数据\n merged.b[key] = counts;\n } else {\n // 如果原数据中有这个分支,逐个元素累加\n merged.b[key] = counts.map((count, i) => (merged.b[key][i] || 0) + count);\n }\n }\n\n return merged;\n }\n}\n","/**\n * 覆盖率差异计算器\n * \n * 负责计算增量覆盖率,这是插件的核心模块\n * \n * 工作流程:\n * 1. 加载 baseline 覆盖率(参考基准)\n * 2. 使用 istanbul-diff 计算差异\n * 3. 结合 Git diff 获取变更的文件和行号\n * 4. 计算每个变更文件的覆盖率\n * 5. 汇总整体覆盖率\n * 6. 保存新的 baseline(可选)\n * \n * @module differ\n */\n\nimport * as istanbulDiff from 'istanbul-diff';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { IncrementalCoverageOptions, CoverageMap, IncrementalCoverageResult } from './types';\nimport { getGitDiff } from './git';\n\n/**\n * Coverage Differ - 覆盖率差异计算器\n * \n * 核心职责:\n * 1. 管理 baseline 覆盖率(加载/保存)\n * 2. 使用 istanbul-diff 计算覆盖率差异\n * 3. 结合 Git diff 确定需要检查的代码行\n * 4. 计算增量覆盖率百分比\n * \n * @example\n * const differ = new CoverageDiffer(options);\n * const result = await differ.calculate(currentCoverage);\n */\nexport class CoverageDiffer {\n constructor(private options: IncrementalCoverageOptions) { }\n\n /**\n * 计算增量覆盖率\n * \n * 这是最核心的方法,完整的计算流程:\n * \n * 1. 加载 baseline(如果存在)\n * 2. 使用 istanbul-diff 计算差异\n * 3. 获取 Git 变更的文件和行号\n * 4. 对每个变更文件:\n * a. 获取该文件的覆盖率数据\n * b. 找出变更的行号\n * c. 检查这些行是否被覆盖\n * d. 计算覆盖率百分比\n * 5. 汇总所有文件的覆盖率\n * 6. 保存新的 baseline(如果需要)\n * \n * @param currentCoverage - 当前的覆盖率数据\n * @returns 增量覆盖率计算结果\n * \n * @example\n * const result = await differ.calculate(coverageMap);\n * console.log(`Overall coverage: ${result.overall.coverageRate}%`);\n */\n async calculate(currentCoverage: CoverageMap): Promise<IncrementalCoverageResult> {\n console.log('[CoverageDiffer] 开始计算增量覆盖率...');\n\n // 1. 加载 baseline 覆盖率\n // baseline 是参考基准,通常是上一次的覆盖率或主分支的覆盖率\n const baseline = this.loadBaseline();\n\n // 2. 使用 istanbul-diff 计算差异\n // istanbul-diff 会比较 baseline 和当前覆盖率,找出变化\n // pick: 'lines' 表示只关注行覆盖率\n const diff = istanbulDiff.diff(baseline || {}, currentCoverage, {\n pick: 'lines',\n });\n\n console.log('[CoverageDiffer] Istanbul diff 结果:', diff.total);\n\n // 3. 获取 Git diff(变更的文件和行号)\n // 这一步很关键:我们只关心 Git 中变更的代码,而不是所有代码\n console.log('[CoverageDiffer] Git diff base:', this.options.gitDiffBase);\n const gitDiff = await getGitDiff(this.options.gitDiffBase || 'main');\n console.log('[CoverageDiffer] Git diff 返回文件数:', gitDiff.files.length);\n console.log('[CoverageDiffer] Git diff 文件列表:', gitDiff.files);\n\n // 4. 初始化结果对象\n const result: IncrementalCoverageResult = {\n overall: {\n totalLines: 0, // 总变更行数\n coveredLines: 0, // 已覆盖的变更行数\n coverageRate: 100, // 覆盖率百分比(默认 100%)\n },\n files: [], // 每个文件的详细信息\n changedFiles: gitDiff.files, // 变更的文件列表\n timestamp: Date.now(), // 时间戳\n };\n\n // 5. 归一化当前覆盖率数据(处理路径不一致问题)\n const normalizedCoverage: CoverageMap = {};\n const projectRoot = process.cwd();\n for (const [key, data] of Object.entries(currentCoverage)) {\n const absPath = path.isAbsolute(key) ? key : path.resolve(projectRoot, key);\n normalizedCoverage[absPath] = data;\n }\n\n // 6. 处理每个变更的文件\n let validFileCount = 0;\n console.log(`[CoverageDiffer] 处理 ${gitDiff.files.length} 个 Git 变更文件...`);\n for (const file of gitDiff.files) {\n console.log(`[CoverageDiffer] 检查文件: ${file}`);\n // 过滤非源码文件\n const isValid = this.isValidSourceFile(file);\n console.log(`[CoverageDiffer] 文件有效性: ${isValid}`);\n if (!isValid) continue;\n\n validFileCount++;\n const changedLines = gitDiff.additions[file] || [];\n console.log(`[CoverageDiffer] 变更行数: ${changedLines.length}`);\n if (changedLines.length === 0) continue; // 无新增行,跳过\n\n // 查找覆盖率数据:尝试绝对路径和相对路径匹配\n let coverage = normalizedCoverage[file];\n if (!coverage) {\n // 尝试相对路径匹配\n const relativeFile = path.relative(projectRoot, file);\n coverage = currentCoverage[relativeFile] || currentCoverage[file];\n }\n\n if (!coverage) {\n // 情况 1:文件没有覆盖率数据\n result.files.push({\n file,\n changedLines,\n uncoveredLines: changedLines,\n coverageRate: 0,\n });\n result.overall.totalLines += changedLines.length;\n continue;\n }\n\n // 情况 2:文件有覆盖率数据\n const uncoveredLines = this.getUncoveredLines(coverage, changedLines);\n const coveredCount = changedLines.length - uncoveredLines.length;\n const coverageRate = Math.round((coveredCount / changedLines.length) * 100);\n\n result.files.push({\n file,\n changedLines,\n uncoveredLines,\n coverageRate,\n });\n\n result.overall.totalLines += changedLines.length;\n result.overall.coveredLines += coveredCount;\n }\n\n // 7. 计算整体覆盖率\n if (result.overall.totalLines > 0) {\n result.overall.coverageRate = Math.round(\n (result.overall.coveredLines / result.overall.totalLines) * 100\n );\n }\n\n console.log(`[CoverageDiffer] 计算完成: ${validFileCount} 个有效文件, ${result.overall.totalLines} 变更行, 覆盖率 ${result.overall.coverageRate}%`);\n\n // 7. 保存 baseline(如果需要)\n // 只在首次运行时保存,避免每次都更新 baseline\n if (this.options.autoSaveBaseline && !baseline) {\n console.log('[CoverageDiffer] 保存 baseline...');\n this.saveBaseline(currentCoverage);\n }\n\n return result;\n }\n\n /**\n * 从文件加载 baseline 覆盖率\n * \n * Baseline 是参考基准,用于对比当前覆盖率的变化\n * 通常在首次运行时创建,之后保持不变(除非手动更新)\n * \n * @returns baseline 覆盖率数据,如果文件不存在则返回 null\n * @private\n */\n private loadBaseline(): CoverageMap | null {\n try {\n const baselinePath = this.options.baselinePath || '.coverage/baseline.json';\n if (fs.existsSync(baselinePath)) {\n return JSON.parse(fs.readFileSync(baselinePath, 'utf-8'));\n }\n } catch (error) {\n console.warn('[CoverageDiffer] 加载 baseline 失败:', error);\n }\n return null;\n }\n\n /**\n * 保存 baseline 覆盖率到文件\n * \n * 将当前的覆盖率数据保存为 baseline,供后续对比使用\n * \n * 注意:\n * - 会自动创建目录(如果不存在)\n * - 使用 JSON 格式,便于查看和调试\n * - 格式化输出(2 空格缩进)\n * \n * @param coverage - 要保存的覆盖率数据\n * @private\n */\n private saveBaseline(coverage: CoverageMap): void {\n try {\n const baselinePath = this.options.baselinePath || '.coverage/baseline.json';\n const dir = path.dirname(baselinePath);\n\n // 确保目录存在\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n // 保存为格式化的 JSON\n fs.writeFileSync(baselinePath, JSON.stringify(coverage, null, 2));\n console.log('[CoverageDiffer] Baseline 已保存');\n } catch (error) {\n console.error('[CoverageDiffer] 保存 baseline 失败:', error);\n }\n }\n\n /**\n * 获取未覆盖的行号列表\n * \n * 算法说明:\n * 1. 遍历每一个变更的行号\n * 2. 检查是否有语句(statement)覆盖这一行\n * 3. 如果有语句覆盖且执行次数 > 0,则该行被覆盖\n * 4. 否则,该行未被覆盖\n * \n * 为什么检查 statementMap?\n * - Istanbul 的覆盖率是基于语句(statement)的\n * - statementMap 记录了每个语句的位置(起始行、结束行)\n * - 通过检查语句的位置,可以知道某一行是否被覆盖\n * \n * 为什么检查执行次数?\n * - coverage.s[stmtId] 记录了语句的执行次数\n * - 如果执行次数 > 0,说明这个语句被执行过\n * - 如果执行次数 = 0,说明这个语句没有被执行\n * \n * @param coverage - 文件的覆盖率数据\n * @param changedLines - 变更的行号列表\n * @returns 未覆盖的行号列表\n * \n * @example\n * const uncovered = this.getUncoveredLines(coverage, [10, 11, 12]);\n * // 返回: [11] (假设第 11 行没有被覆盖)\n * \n * @private\n */\n private getUncoveredLines(coverage: any, changedLines: number[]): number[] {\n const uncovered: number[] = [];\n\n // 遍历每一个变更的行\n for (const line of changedLines) {\n let isCovered = false;\n\n // 检查是否有语句覆盖这一行\n // statementMap 的格式:{ '0': { start: { line: 1 }, end: { line: 3 } } }\n for (const [stmtId, loc] of Object.entries(coverage.statementMap || {})) {\n const location = loc as any;\n\n // 检查这个语句是否包含当前行\n // 例如:语句从第 1 行到第 3 行,那么第 2 行也被这个语句覆盖\n if (location.start.line <= line && location.end.line >= line) {\n // 检查这个语句是否被执行过\n if (coverage.s[stmtId] > 0) {\n isCovered = true;\n break; // 找到一个覆盖的语句就够了\n }\n }\n }\n\n // 如果没有找到覆盖的语句,记录为未覆盖\n if (!isCovered) {\n uncovered.push(line);\n }\n }\n\n return uncovered;\n }\n /**\n * 判断是否为有效的源码文件\n * 用于过滤 node_modules, lockfiles 和非源码文件\n * @private\n */\n private isValidSourceFile(file: string): boolean {\n // 1. 排除 node_modules 和基础配置文件\n if (file.includes('node_modules')) return false;\n if (file.includes('package.json') || file.includes('pnpm-lock.yaml') || file.includes('yarn.lock')) return false;\n if (file.endsWith('.d.ts')) return false;\n\n // 2. 检查扩展名\n const ext = path.extname(file).toLowerCase();\n if (!ext) return false;\n const validExts = [\n '.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte',\n '.mjs', '.cjs', '.mts', '.cts'\n ];\n if (!validExts.includes(ext)) return false;\n\n // 3. 获取项目相对路径\n // IMPORTANT: 使用与 git.ts 相同的逻辑 - git root 而不是 process.cwd()\n // 因为 git diff 返回的是相对于 git root 的路径\n const projectRoot = process.cwd(); // TODO: should use git root \n const relativePath = path.isAbsolute(file)\n ? path.relative(projectRoot, file)\n : file;\n\n // 如果路径包含 \"..\",说明在项目目录之外,排除\n if (relativePath.startsWith('..')) {\n return false;\n }\n\n // 4. 应用 include/exclude 配置\n const { include, exclude } = this.options;\n\n // 简单匹配函数:支持通配符 * 和 **\n const isMatch = (pattern: string, target: string) => {\n if (!pattern) return false;\n // 处理 pattern:如果是 src/** 且 target 是 packages/pkg/src/App.vue,也应该尝试匹配\n // 这里我们做一个增强:如果 pattern 不以 / 或 **/ 开头,我们自动允许它匹配深层目录\n let effectivePattern = pattern;\n if (!pattern.startsWith('/') && !pattern.startsWith('**/')) {\n effectivePattern = '**/' + pattern;\n }\n\n // IMPORTANT: 先替换通配符,再转义特殊字符\n // 1. 先用占位符替换通配符\n const regexStr = effectivePattern\n .replace(/\\*\\*/g, '<GLOBSTAR>') // ** -> 占位符\n .replace(/\\*/g, '<STAR>') // * -> 占位符\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // 转义特殊字符\n .replace(/<GLOBSTAR>/g, '.*') // 占位符 -> .*\n .replace(/<STAR>/g, '[^/]*'); // 占位符 -> [^/]*\n const regex = new RegExp(`^${regexStr}$`);\n return regex.test(target) || regex.test('/' + target);\n };\n\n // Exclude 优先\n if (exclude) {\n const excludes = Array.isArray(exclude) ? exclude : [exclude];\n for (const pattern of excludes) {\n if (isMatch(pattern, relativePath)) {\n return false;\n }\n }\n }\n\n // Include 过滤\n if (include) {\n const includes = Array.isArray(include) ? include : [include];\n let matched = false;\n for (const pattern of includes) {\n if (isMatch(pattern, relativePath)) {\n matched = true;\n break;\n }\n }\n if (!matched) {\n return false;\n }\n }\n\n return true;\n }\n}\n","/**\n * Git 服务模块\n * \n * 负责与 Git 交互,获取代码变更信息\n * \n * 核心功能:\n * 1. 获取变更的文件列表\n * 2. 解析 Git diff 输出\n * 3. 提取新增和删除的行号\n * \n * @module git\n */\n\nimport simpleGit from 'simple-git';\nimport * as path from 'path';\nimport type { GitDiffResult } from './types';\n\n// 初始化 simple-git 实例\n// 我们先创建一个基础实例,用于寻找 Git 根目录\nconst baseGit = simpleGit();\n\n/**\n * 获取 Git 根目录\n */\nasync function getGitRoot(): Promise<string> {\n try {\n const topLevel = await baseGit.revparse(['--show-toplevel']);\n return topLevel.trim();\n } catch (e) {\n return process.cwd();\n }\n}\n\n/**\n * 获取 Git diff 信息\n * \n * 对比当前分支(HEAD)与指定基准分支的差异\n * 返回变更的文件列表和具体的行号信息\n * \n * 工作流程:\n * 1. 使用 git.diffSummary() 获取变更文件列表\n * 2. 对每个文件使用 git.diff() 获取详细差异\n * 3. 解析 diff 输出,提取行号\n * \n * @param base - 对比的基准分支(例如:'main', 'develop')\n * @returns Git diff 结果,包含文件列表和行号信息\n * \n * @example\n * const diff = await getGitDiff('main');\n * console.log('Changed files:', diff.files);\n * console.log('Added lines in file.ts:', diff.additions['file.ts']);\n */\nexport async function getGitDiff(base: string = 'main'): Promise<GitDiffResult> {\n try {\n // 获取 Git 根目录\n const topLevel = await getGitRoot();\n // 创建在该 root 下运行的实例\n const git = simpleGit(topLevel);\n\n // 1. 获取变更的文件列表\n // 使用 --name-only 可以获得纯净的文件路径,避免 diffSummary 中可能出现的 {a => b} 格式\n const filesRaw = await git.raw(['diff', '--name-only', base]);\n const changedFiles = filesRaw.split('\\n').filter(f => f.trim().length > 0);\n\n // 初始化结果对象\n const result: GitDiffResult = {\n files: [], // 变更的文件列表\n additions: {}, // 每个文件新增的行号\n deletions: {}, // 每个文件删除的行号\n };\n\n // 2. 遍历变更的文件\n for (const relativeFile of changedFiles) {\n const absoluteFile = path.resolve(topLevel, relativeFile);\n\n // 记录绝对路径\n result.files.push(absoluteFile);\n\n // 获取该文件的详细 diff\n const diff = await git.diff([base, '--', relativeFile]);\n\n // 解析 diff 输出,提取行号\n const { additions, deletions } = parseDiff(diff);\n\n // 记录行号信息\n if (additions.length > 0) {\n result.additions[absoluteFile] = additions;\n }\n if (deletions.length > 0) {\n result.deletions[absoluteFile] = deletions;\n }\n }\n\n return result;\n } catch (error) {\n // 如果 Git 操作失败(例如:不在 Git 仓库中),返回空结果\n console.warn('[Git] 获取 diff 失败:', error);\n return {\n files: [],\n additions: {},\n deletions: {},\n };\n }\n}\n\n/**\n * 解析 Git diff 输出,提取行号\n * \n * Git diff 格式说明:\n * ```\n * @@ -10,5 +10,6 @@ ← hunk header(块头)\n * -deleted line ← 删除的行(以 - 开头)\n * +added line ← 新增的行(以 + 开头)\n * context line ← 上下文行(以空格开头)\n * ```\n * \n * 算法说明:\n * 1. 查找 hunk header(@@ ... @@)\n * 2. 从 header 中提取起始行号\n * 3. 遍历每一行:\n * - '+' 开头:新增行,记录行号并递增\n * - '-' 开头:删除行,记录行号但不递增\n * - ' ' 开头:上下文行,递增行号\n * \n * 注意事项:\n * - '+++' 和 '---' 是文件头,不是实际的变更\n * - 行号从 1 开始(不是 0)\n * - 删除的行不影响新文件的行号\n * \n * @param diffText - Git diff 的原始文本输出\n * @returns 新增和删除的行号列表\n * \n * @example\n * const diff = `\n * @@ -10,3 +10,4 @@\n * context\n * -old line\n * +new line 1\n * +new line 2\n * `;\n * const result = parseDiff(diff);\n * // result.additions = [11, 12]\n * // result.deletions = [11]\n * \n * @private\n */\nexport function parseDiff(diffText: string): { additions: number[]; deletions: number[] } {\n const additions: number[] = [];\n const deletions: number[] = [];\n\n // 按行分割 diff 文本\n const lines = diffText.split('\\n');\n\n // 当前处理的行号(新文件中的行号)\n let currentLine = 0;\n\n for (const line of lines) {\n // 1. 解析 hunk header\n // 格式:@@ -old_start,old_count +new_start,new_count @@\n // 例如:@@ -10,5 +10,6 @@ 表示:\n // - 旧文件从第 10 行开始,共 5 行\n // - 新文件从第 10 行开始,共 6 行\n if (line.startsWith('@@')) {\n // 提取新文件的起始行号(+后面的数字)\n const match = line.match(/\\+(\\d+)/);\n if (match) {\n currentLine = parseInt(match[1], 10);\n }\n continue;\n }\n\n // 2. 跳过非内容行\n // 例如:文件头(diff --git)、索引行(index)等\n if (!line.startsWith('+') && !line.startsWith('-') && !line.startsWith(' ')) {\n continue;\n }\n\n // 3. 处理新增行\n if (line.startsWith('+') && !line.startsWith('+++')) {\n // '+' 开头表示新增的行\n // '+++' 是文件头,需要排除\n additions.push(currentLine);\n currentLine++; // 新增行会增加行号\n }\n // 4. 处理删除行\n else if (line.startsWith('-') && !line.startsWith('---')) {\n // '-' 开头表示删除的行\n // '---' 是文件头,需要排除\n deletions.push(currentLine);\n // 注意:删除的行不增加行号,因为它在新文件中不存在\n }\n // 5. 处理上下文行\n else {\n // 空格开头表示上下文行(未变更的行)\n currentLine++;\n }\n }\n\n return { additions, deletions };\n}\n\n/**\n * 获取变更的文件列表\n * \n * 这是 getGitDiff 的简化版本,只返回文件列表\n * 适用于只需要知道哪些文件变更了,不需要具体行号的场景\n * \n * @param base - 对比的基准分支\n * @returns 变更的文件路径列表\n * \n * @example\n * const files = await getChangedFiles('main');\n * console.log('Changed files:', files);\n */\nexport async function getChangedFiles(base: string = 'main'): Promise<string[]> {\n const diff = await getGitDiff(base);\n return diff.files;\n}\n","/**\n * 覆盖率报告生成器\n * \n * 负责生成各种格式的覆盖率报告\n * \n * 核心功能:\n * 1. 生成 HTML 格式的可视化报告\n * 2. 生成 JSON 格式的数据报告\n * 3. 维护报告文件(最新报告、历史报告及自动清理)\n * \n * @module reporter\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { IncrementalCoverageOptions, IncrementalCoverageResult } from './types';\n\n/**\n * Coverage Reporter - 覆盖率报告生成器\n */\nexport class CoverageReporter {\n private outputDir: string;\n\n constructor(private options: IncrementalCoverageOptions) {\n this.outputDir = path.resolve(process.cwd(), options.outputDir || '.coverage');\n }\n\n /**\n * 生成增量覆盖率报告\n * \n * @param result 增量计算结果\n * @returns 报告保存的路径\n */\n async generate(result: IncrementalCoverageResult): Promise<string> {\n console.log('[CoverageReporter] 开始生成报告...');\n\n // 1. 确保输出目录存在\n if (!fs.existsSync(this.outputDir)) {\n fs.mkdirSync(this.outputDir, { recursive: true });\n }\n\n // 2. 读取源代码(用于报告展示)\n result.files.forEach(fileData => {\n try {\n if (fs.existsSync(fileData.file)) {\n fileData.sourceCode = fs.readFileSync(fileData.file, 'utf-8');\n }\n } catch (e) {\n console.warn(`[CoverageReporter] 无法读取源码: ${fileData.file}`);\n }\n });\n\n // 3. 生成 HTML 内容\n const html = this.renderHtml(result);\n\n // 3. 多路径持久化\n const timestamp = result.timestamp || Date.now();\n const date = new Date(timestamp);\n\n // 生成本地化时间戳字符串作为文件名的一部分: 20231027-143005\n const dateStr = date.getFullYear() +\n String(date.getMonth() + 1).padStart(2, '0') +\n String(date.getDate()).padStart(2, '0') + '-' +\n String(date.getHours()).padStart(2, '0') +\n String(date.getMinutes()).padStart(2, '0') +\n String(date.getSeconds()).padStart(2, '0');\n\n const latestPath = path.join(this.outputDir, 'latest.html');\n const historyPath = path.join(this.outputDir, `report-${dateStr}.html`);\n\n try {\n // 写入最新报告\n fs.writeFileSync(latestPath, html, 'utf-8');\n // 写入历史报告\n fs.writeFileSync(historyPath, html, 'utf-8');\n\n console.log(`[CoverageReporter] 报告已生成: ${latestPath}`);\n console.log(`[CoverageReporter] 细节报告: ${historyPath}`);\n\n // 4. 清理旧报告\n this.cleanupOldReports();\n\n // 5. 如果配置了 JSON,也可以在这里扩展\n if (this.options.reportFormat === 'json' || this.options.reportFormat === 'both') {\n const jsonPath = path.join(this.outputDir, `report-${dateStr}.json`);\n fs.writeFileSync(jsonPath, JSON.stringify(result, null, 2), 'utf-8');\n }\n\n // 6. 阈值检查 (Gatekeeping)\n const threshold = this.options.threshold || 80;\n if (result.overall.coverageRate < threshold) {\n const errorMsg = `[IncrementalCoverage] ❌ 增量覆盖率评估未通过: 当前为 ${result.overall.coverageRate}%, 阈值要求为 ${threshold}%`;\n console.error('\\x1b[31m%s\\x1b[0m', errorMsg); // 红色输出\n\n // 如果在 CI 环境下,直接抛出错误以阻断流程\n if (process.env.CI || this.options.failOnError) {\n throw new Error(errorMsg);\n }\n } else {\n console.log('\\x1b[32m%s\\x1b[0m', `[IncrementalCoverage] ✅ 增量覆盖率评估通过: ${result.overall.coverageRate}%`);\n }\n\n return latestPath;\n } catch (error) {\n console.error('[CoverageReporter] 写入报告文件失败:', error);\n throw error;\n }\n }\n\n /**\n * 渲染 HTML 模板\n * \n * @param result 计算结果\n * @private\n */\n private renderHtml(result: IncrementalCoverageResult): string {\n const threshold = this.options.threshold || 80;\n const isPassed = result.overall.coverageRate >= threshold;\n const statusColor = isPassed ? '#4caf50' : '#f44336';\n const statusText = isPassed ? '通过' : '未达标';\n\n // 序列化文件数据,供前端 JS 使用\n const filesData = JSON.stringify(result.files.map(f => ({\n file: f.file,\n sourceCode: f.sourceCode || '',\n uncoveredLines: f.uncoveredLines\n })));\n\n return `\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>增量覆盖率报告 - ${new Date(result.timestamp).toLocaleString()}</title>\n <link href=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css\" rel=\"stylesheet\" />\n <style>\n :root { --primary: #667eea; --success: #4caf50; --danger: #f44336; --warning: #f6ad55; }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif; background: #f4f7f9; color: #333; line-height: 1.6; }\n .container { max-width: 1200px; margin: 40px auto; background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.08); overflow: hidden; }\n \n /* Header */\n .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 40px; }\n .header h1 { font-size: 32px; font-weight: 800; margin-bottom: 8px; display: flex; align-items: center; }\n .header .timestamp { opacity: 0.8; font-size: 14px; }\n \n /* Grid */\n .summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px; padding: 30px; border-bottom: 1px solid #eee; }\n .card { background: #f8fafc; padding: 25px; border-radius: 12px; border: 1px solid #e2e8f0; text-align: center; }\n .card-title { font-size: 13px; font-weight: 600; color: #64748b; text-transform: uppercase; margin-bottom: 8px; }\n .card-value { font-size: 36px; font-weight: 800; color: #1e293b; }\n .status-badge { padding: 4px 12px; border-radius: 20px; font-size: 14px; color: white; background: ${statusColor}; }\n\n /* Table */\n .file-list { padding: 30px; }\n table { width: 100%; border-collapse: collapse; }\n th { text-align: left; padding: 15px; border-bottom: 2px solid #edf2f7; color: #4a5568; }\n td { padding: 15px; border-bottom: 1px solid #edf2f7; vertical-align: middle; }\n .file-path { cursor: pointer; color: var(--primary); font-family: monospace; font-weight: 600; }\n .file-path:hover { text-decoration: underline; }\n .progress-bar { height: 6px; background: #e2e8f0; border-radius: 3px; overflow: hidden; width: 80px; display: inline-block; vertical-align: middle; margin-right: 8px; }\n .uncovered-tag { font-size: 12px; color: var(--danger); background: #fff5f5; padding: 2px 6px; border-radius: 4px; cursor: pointer; }\n\n /* Modal */\n .modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); }\n .modal-content { background-color: #fff; margin: 2% auto; width: 90%; height: 90%; border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); }\n .modal-header { padding: 20px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; background: #f8fafc; }\n .close { color: #aaa; font-size: 28px; font-weight: bold; cursor: pointer; }\n .close:hover { color: black; }\n .code-container { flex: 1; overflow: auto; position: relative; background: #2d2d2d; }\n \n /* Line Highlighting */\n .line-highlight { display: block; width: 100%; }\n .line-uncovered { background-color: rgba(244, 67, 54, 0.2); border-left: 4px solid #f44336; }\n .line-covered { background-color: rgba(76, 175, 80, 0.1); border-left: 4px solid #4caf50; }\n \n /* Prism Overrides */\n pre[class*=\"language-\"] { margin: 0; border-radius: 0; padding: 0; }\n code[class*=\"language-\"] { font-family: \"JetBrains Mono\", Consolas, monospace; font-size: 14px; line-height: 1.5; }\n .token-line { display: block; padding: 0 1em; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"header\">\n <h1>增量覆盖率报告</h1>\n <div class=\"timestamp\">生成时间: ${new Date(result.timestamp).toLocaleString()}</div>\n </div>\n \n <div class=\"summary-grid\">\n <div class=\"card\">\n <div class=\"card-title\">增量覆盖率</div>\n <div class=\"card-value\">${result.overall.coverageRate}%</div>\n </div>\n <div class=\"card\">\n <div class=\"card-title\">变更总行数</div>\n <div class=\"card-value\">${result.overall.totalLines}</div>\n </div>\n <div class=\"card\">\n <div class=\"card-title\">覆盖行数</div>\n <div class=\"card-value\">${result.overall.coveredLines}</div>\n </div>\n <div class=\"card\">\n <div class=\"card-title\">结果状态</div>\n <div class=\"card-value\"><span class=\"status-badge\">${statusText}</span></div>\n </div>\n </div>\n\n <div class=\"file-list\">\n <table>\n <thead>\n <tr>\n <th>文件路径</th>\n <th width=\"200\">覆盖率</th>\n <th>未覆盖行号 (点击跳转)</th>\n </tr>\n </thead>\n <tbody>\n ${result.files.map((file, index) => {\n const filePass = file.coverageRate >= threshold;\n const barColor = filePass ? '#4caf50' : '#f6ad55';\n return `\n <tr>\n <td><span class=\"file-path\" onclick=\"openModal(${index})\">${file.file}</span></td>\n <td>\n <div class=\"progress-bar\"><div class=\"progress-fill\" style=\"width: ${file.coverageRate}%; height: 100%; background: ${barColor};\"></div></div>\n <strong>${file.coverageRate}%</strong>\n </td>\n <td>\n ${file.uncoveredLines.length > 0\n ? file.uncoveredLines.map(line =>\n `<span class=\"uncovered-tag\" onclick=\"openModal(${index}, ${line})\">${line}</span>`\n ).join(', ')\n : '<span style=\"color: #48bb78\">✅ 全覆盖</span>'}\n </td>\n </tr>`;\n }).join('')}\n </tbody>\n </table>\n </div>\n \n <div class=\"footer\">Generated by Incremental Coverage Plugin</div>\n </div>\n\n <!-- Code Modal -->\n <div id=\"codeModal\" class=\"modal\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h3 id=\"modalTitle\" style=\"font-family: monospace\">FileName.js</h3>\n <span class=\"close\" onclick=\"closeModal()\">×</span>\n </div>\n <div class=\"code-container\">\n <pre><code id=\"codeBlock\" class=\"language-javascript\"></code></pre>\n </div>\n </div>\n </div>\n\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-typescript.min.js\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-jsx.min.js\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-tsx.min.js\"></script>\n \n <script>\n const filesData = ${filesData};\n\n function openModal(index, lineToScroll) {\n const file = filesData[index];\n const modal = document.getElementById('codeModal');\n const title = document.getElementById('modalTitle');\n const codeBlock = document.getElementById('codeBlock');\n \n title.innerText = file.file;\n modal.style.display = 'block';\n \n // 简单的 HTML 转义\n const safeCode = file.sourceCode\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n\n // 处理代码行高亮\n // 为了配合 Prism,我们需要先生成带行号的结构,或者手动处理\n // 简单方案:先用 Prism 高亮,然后 split 成行,再包裹 div\n \n // 1. 设置原始代码让 Prism 高亮\n // 注意:Prism.highlight 需要同步加载语言包\n const ext = file.file.split('.').pop();\n let lang = Prism.languages.javascript;\n if (ext === 'ts' || ext === 'tsx') lang = Prism.languages.typescript;\n // if (ext === 'vue') ... vue 通常包含 script, 需要特殊处理,这里简化视为 js/ts\n\n const highlighted = Prism.highlight(file.sourceCode, lang, 'javascript');\n \n // 2. 将高亮后的 HTML 按行分割并包裹\n const lines = highlighted.split('\\\\n');\n const numberedHtml = lines.map((lineContent, i) => {\n const lineNum = i + 1;\n let className = 'token-line';\n if (file.uncoveredLines.includes(lineNum)) {\n className += ' line-uncovered';\n }\n return \\`<div id=\"L\\${lineNum}\" class=\"\\${className}\">\\${lineContent || ' '}</div>\\`;\n }).join('');\n\n codeBlock.innerHTML = numberedHtml;\n \n // 3. 滚动到指定行\n if (lineToScroll) {\n setTimeout(() => {\n const el = document.getElementById('L' + lineToScroll);\n if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });\n }, 100);\n }\n }\n\n function closeModal() {\n document.getElementById('codeModal').style.display = 'none';\n }\n\n // 点击窗口外部关闭\n window.onclick = function(event) {\n const modal = document.getElementById('codeModal');\n if (event.target == modal) {\n closeModal();\n }\n }\n </script>\n</body>\n</html>\n `.trim();\n }\n\n /**\n * 清理超出保留数量的历史报告\n * @private\n */\n private cleanupOldReports(): void {\n try {\n if (!fs.existsSync(this.outputDir)) return;\n\n const files = fs.readdirSync(this.outputDir)\n .filter(f => f.startsWith('report-') && (f.endsWith('.html') || f.endsWith('.json')))\n .map(f => ({\n name: f,\n path: path.join(this.outputDir, f),\n mtime: fs.statSync(path.join(this.outputDir, f)).mtime.getTime()\n }))\n .sort((a, b) => b.mtime - a.mtime); // 按修改时间降序\n\n const limit = this.options.historyCount || 15;\n if (files.length > limit) {\n const toDelete = files.slice(limit);\n console.log(`[CoverageReporter] 正在清理旧报告 (超出限制 ${limit})...`);\n for (const file of toDelete) {\n fs.unlinkSync(file.path);\n console.log(`[CoverageReporter] 已删除: ${file.name}`);\n }\n }\n } catch (error) {\n console.error('[CoverageReporter] 清理旧报告失败:', error);\n }\n }\n}\n"],"mappings":"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","names":["path","path","simpleGit","diff","diff","fs","path","compiler"]}
|