@@ -3,6 +3,7 @@ import { glob } from 'glob'
33import { CoberturaCoverageParser , type CoverageReport } from '../coverage/index.js'
44import { applyFilters , getChangedLinesFromGit , type ChangedLinesMap , type FilterContext } from '../filter/index.js'
55import { generateMarkdown } from '../markdown/index.js'
6+ import { resolveFilePaths , type PathResolutionContext } from '../path/index.js'
67
78/**
89 * Logger interface for dependency injection.
@@ -19,6 +20,8 @@ export type Logger = {
1920export type ProcessCoverageInputs = {
2021 /** Coverage file patterns (newline or comma separated) */
2122 files : string
23+ /** Source directory for resolving file paths from coverage files */
24+ sourceDir : string
2225 /** Whether to filter to show only changed lines */
2326 showChangedLinesOnly : boolean
2427 /** Glob pattern to filter which files to show */
@@ -86,9 +89,37 @@ export async function processCoverage(
8689 reports . push ( report )
8790 }
8891
89- // Merge all reports into one
92+ // Merge all reports into one (including sources)
9093 const mergedReport = mergeReports ( reports )
9194
95+ // Resolve file paths to get display paths and absolute paths
96+ const allFilenames : string [ ] = [ ]
97+ for ( const pkg of mergedReport . packages ) {
98+ for ( const file of pkg . files ) {
99+ allFilenames . push ( file . filename )
100+ }
101+ }
102+
103+ const pathContext : PathResolutionContext = {
104+ sources : mergedReport . sources ?? [ ] ,
105+ sourceDir : inputs . sourceDir ,
106+ logger,
107+ }
108+
109+ logger . info ( `Resolving file paths (sourceDir: ${ inputs . sourceDir } )...` )
110+ const resolvedPaths = await resolveFilePaths ( allFilenames , pathContext )
111+
112+ // Update file objects with resolved paths
113+ for ( const pkg of mergedReport . packages ) {
114+ for ( const file of pkg . files ) {
115+ const resolution = resolvedPaths . get ( file . filename )
116+ if ( resolution ) {
117+ file . resolvedPath = resolution . absolutePath
118+ file . filename = resolution . displayPath
119+ }
120+ }
121+ }
122+
92123 // Get changed lines using git if filtering is enabled and we have a base ref
93124 let changedLines : ChangedLinesMap | undefined
94125 if ( inputs . showChangedLinesOnly && inputs . baseRef ) {
@@ -118,16 +149,18 @@ export async function processCoverage(
118149 logger . info ( 'Coverage report filtered based on configuration' )
119150 }
120151
121- // Collect all unique file paths from the filtered report
122- const filePaths = new Set < string > ( )
152+ // Collect all unique resolved file paths from the filtered report
153+ // Map from display path (filename) to resolved path for reading
154+ const filePathMap = new Map < string , string > ( )
123155 for ( const pkg of filteredReport . packages ) {
124156 for ( const file of pkg . files ) {
125- filePaths . add ( file . filename )
157+ // Use resolvedPath for reading, fall back to filename if not set
158+ filePathMap . set ( file . filename , file . resolvedPath ?? file . filename )
126159 }
127160 }
128161
129- // Read file contents from disk
130- const fileContents = await readFileContents ( [ ... filePaths ] )
162+ // Read file contents from disk using resolved paths
163+ const fileContents = await readFileContents ( filePathMap )
131164
132165 // Generate markdown from filtered report
133166 const markdown = generateMarkdown ( filteredReport , fileContents )
@@ -140,11 +173,20 @@ export async function processCoverage(
140173
141174/**
142175 * Merge multiple coverage reports into one.
176+ * Also merges sources from all reports.
143177 */
144178function mergeReports ( reports : CoverageReport [ ] ) : CoverageReport {
145179 const packageMap = new Map < string , CoverageReport [ 'packages' ] [ 0 ] > ( )
180+ const allSources = new Set < string > ( )
146181
147182 for ( const report of reports ) {
183+ // Collect sources from all reports
184+ if ( report . sources ) {
185+ for ( const source of report . sources ) {
186+ allSources . add ( source )
187+ }
188+ }
189+
148190 for ( const pkg of report . packages ) {
149191 if ( packageMap . has ( pkg . name ) ) {
150192 // Merge files into existing package
@@ -156,7 +198,11 @@ function mergeReports(reports: CoverageReport[]): CoverageReport {
156198 }
157199 }
158200
159- return { packages : Array . from ( packageMap . values ( ) ) }
201+ const result : CoverageReport = { packages : Array . from ( packageMap . values ( ) ) }
202+ if ( allSources . size > 0 ) {
203+ result . sources = Array . from ( allSources )
204+ }
205+ return result
160206}
161207
162208/**
@@ -195,20 +241,22 @@ function calculateOverallMetrics(report: CoverageReport): CoverageMetrics {
195241}
196242
197243/**
198- * Read file contents from disk for a list of file paths.
199- * Returns a map of filepath -> lines array.
244+ * Read file contents from disk for a map of display paths to resolved paths.
245+ * Returns a map of display path -> lines array.
200246 * Files that don't exist return empty arrays.
247+ *
248+ * @param pathMap - Map of display path to resolved (absolute) path
201249 */
202- async function readFileContents ( filepaths : string [ ] ) : Promise < Map < string , string [ ] > > {
250+ async function readFileContents ( pathMap : Map < string , string > ) : Promise < Map < string , string [ ] > > {
203251 const contents = new Map < string , string [ ] > ( )
204252
205- for ( const filepath of filepaths ) {
253+ for ( const [ displayPath , resolvedPath ] of pathMap ) {
206254 try {
207- const content = await fs . readFile ( filepath , 'utf-8' )
208- contents . set ( filepath , content . split ( '\n' ) )
255+ const content = await fs . readFile ( resolvedPath , 'utf-8' )
256+ contents . set ( displayPath , content . split ( '\n' ) )
209257 } catch {
210258 // File doesn't exist or can't be read - use empty array
211- contents . set ( filepath , [ ] )
259+ contents . set ( displayPath , [ ] )
212260 }
213261 }
214262
0 commit comments