diff --git a/package-lock.json b/package-lock.json index b9807b8..45ad781 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "coverage-visualizer", - "version": "1.0.1", + "version": "1.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "coverage-visualizer", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "dependencies": { "sql.js": "^1.14.1" diff --git a/package.json b/package.json index cbae9ab..3b8c5e3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "coverage-visualizer", "displayName": "Python Coverage Visualizer", "description": "Visualize Python test coverage inline in VS Code — highlights, CodeLens, dashboard, and sidebar tree view", - "version": "1.0.1", + "version": "1.0.2", "publisher": "kool7", "engines": { "vscode": "^1.90.0" diff --git a/src/extension.ts b/src/extension.ts index 04664de..56424a9 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -203,6 +203,9 @@ async function handleNoCoverage(workspaceFolder: string) { proc.on('close', code => { coverageRunInProgress = false; coverageOutputChannel!.appendLine(`\n[exited ${code ?? '?'}]`); + if (code !== 0) { + vscode.window.showWarningMessage('pytest failed — check the Coverage Run output panel.'); + } }); } finally { noCoveragePromptActive = false; @@ -230,6 +233,16 @@ async function detectAndParse( const sqlitePath = path.join(workspaceFolder, '.coverage'); if (fs.existsSync(sqlitePath)) { + const python = resolvePython(workspaceFolder); + const generated = await new Promise(resolve => { + exec(`"${python}" -m coverage json`, { cwd: workspaceFolder }, err => resolve(!err)); + }); + if (generated && fs.existsSync(jsonPath)) { + try { + const raw = JSON.parse(fs.readFileSync(jsonPath, 'utf-8')) as RawCoverageJson; + return { report: parseCoverageJson(raw), formatUsed: 'coverage.json' }; + } catch { /* fall through */ } + } try { return { report: await parseCoverageSqlite(sqlitePath, workspaceFolder), formatUsed: '.coverage' }; } catch (err) { diff --git a/src/parsers/coverageParser.ts b/src/parsers/coverageParser.ts index 3beba16..a6019f6 100644 --- a/src/parsers/coverageParser.ts +++ b/src/parsers/coverageParser.ts @@ -289,9 +289,9 @@ export function findFileInReport( report: CoverageReport, absolutePath: string ): FileCoverage | undefined { - const normalized = absolutePath.replace(/\\/g, '/'); + const normalized = absolutePath.replace(/\\/g, '/').toLowerCase(); return Object.entries(report.files).find(([key]) => { - const normalizedKey = key.replace(/\\/g, '/'); + const normalizedKey = key.replace(/\\/g, '/').toLowerCase(); return normalized.endsWith(normalizedKey) || normalized.includes(normalizedKey); })?.[1]; } diff --git a/src/ui/dashboardPanel.ts b/src/ui/dashboardPanel.ts index b5edbc5..6e87a78 100644 --- a/src/ui/dashboardPanel.ts +++ b/src/ui/dashboardPanel.ts @@ -75,9 +75,17 @@ function colorClass(pct: number, thresholdGood: number, thresholdWarn: number): function buildHtml(report: CoverageReport, webview?: vscode.Webview, extensionUri?: vscode.Uri): string { const { thresholdGood, thresholdWarn, excludeTestFiles } = getConfig(); + const workspaceRoot = (vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? '').replace(/\\/g, '/'); + const files = Object.entries(report.files) .filter(([filePath]) => !excludeTestFiles || !isTestFile(filePath)) - .map(([filePath, data]) => ({ filePath, ...data })) + .map(([filePath, data]) => { + const normalized = filePath.replace(/\\/g, '/'); + const displayPath = workspaceRoot && normalized.toLowerCase().startsWith(workspaceRoot.toLowerCase()) + ? normalized.slice(workspaceRoot.length).replace(/^\//, '') + : normalized; + return ({ filePath, displayPath, ...data }); + }) .sort((a, b) => a.percentCovered - b.percentCovered); const coveredStatements = files.reduce((n, f) => n + f.executedLines.length, 0); @@ -89,8 +97,8 @@ function buildHtml(report: CoverageReport, webview?: vscode.Webview, extensionUr const cls = colorClass(f.percentCovered, thresholdGood, thresholdWarn); const total = f.executedLines.length + f.missingLines.length; return ` - - ${f.filePath} + + ${f.displayPath} ${f.executedLines.length}/${total}