1 | const Exclude = require('test-exclude')
|
2 | const libCoverage = require('istanbul-lib-coverage')
|
3 | const libReport = require('istanbul-lib-report')
|
4 | const reports = require('istanbul-reports')
|
5 | const { readdirSync, readFileSync, statSync } = require('fs')
|
6 | const { isAbsolute, resolve, extname } = require('path')
|
7 | const { pathToFileURL, fileURLToPath } = require('url')
|
8 | const getSourceMapFromFile = require('./source-map-from-file')
|
9 |
|
10 | const v8toIstanbul = require('v8-to-istanbul')
|
11 | const isCjsEsmBridgeCov = require('./is-cjs-esm-bridge')
|
12 | const util = require('util')
|
13 | const debuglog = util.debuglog('c8')
|
14 |
|
15 | class Report {
|
16 | constructor ({
|
17 | exclude,
|
18 | excludeAfterRemap,
|
19 | include,
|
20 | reporter,
|
21 | reportsDirectory,
|
22 | tempDirectory,
|
23 | watermarks,
|
24 | omitRelative,
|
25 | wrapperLength,
|
26 | resolve: resolvePaths,
|
27 | all,
|
28 | src,
|
29 | allowExternal = false,
|
30 | skipFull
|
31 | }) {
|
32 | this.reporter = reporter
|
33 | this.reportsDirectory = reportsDirectory
|
34 | this.tempDirectory = tempDirectory
|
35 | this.watermarks = watermarks
|
36 | this.resolve = resolvePaths
|
37 | this.exclude = new Exclude({
|
38 | exclude: exclude,
|
39 | include: include,
|
40 | relativePath: !allowExternal
|
41 | })
|
42 | this.excludeAfterRemap = excludeAfterRemap
|
43 | this.omitRelative = omitRelative
|
44 | this.sourceMapCache = {}
|
45 | this.wrapperLength = wrapperLength
|
46 | this.all = all
|
47 | this.src = this._getSrc(src)
|
48 | this.skipFull = skipFull
|
49 | }
|
50 |
|
51 | _getSrc (src) {
|
52 | if (typeof src === 'string') {
|
53 | return [src]
|
54 | } else if (Array.isArray(src)) {
|
55 | return src
|
56 | } else {
|
57 | return [process.cwd()]
|
58 | }
|
59 | }
|
60 |
|
61 | async run () {
|
62 | const context = libReport.createContext({
|
63 | dir: this.reportsDirectory,
|
64 | watermarks: this.watermarks,
|
65 | coverageMap: await this.getCoverageMapFromAllCoverageFiles()
|
66 | })
|
67 |
|
68 | for (const _reporter of this.reporter) {
|
69 | reports.create(_reporter, {
|
70 | skipEmpty: false,
|
71 | skipFull: this.skipFull,
|
72 | maxCols: 100
|
73 | }).execute(context)
|
74 | }
|
75 | }
|
76 |
|
77 | async getCoverageMapFromAllCoverageFiles () {
|
78 |
|
79 |
|
80 |
|
81 |
|
82 | if (this._allCoverageFiles) return this._allCoverageFiles
|
83 |
|
84 | const map = libCoverage.createCoverageMap()
|
85 | const v8ProcessCov = this._getMergedProcessCov()
|
86 | const resultCountPerPath = new Map()
|
87 | const possibleCjsEsmBridges = new Map()
|
88 |
|
89 | for (const v8ScriptCov of v8ProcessCov.result) {
|
90 | try {
|
91 | const sources = this._getSourceMap(v8ScriptCov)
|
92 | const path = resolve(this.resolve, v8ScriptCov.url)
|
93 | const converter = v8toIstanbul(path, this.wrapperLength, sources, (path) => {
|
94 | if (this.excludeAfterRemap) {
|
95 | return !this.exclude.shouldInstrument(path)
|
96 | }
|
97 | })
|
98 | await converter.load()
|
99 |
|
100 | if (resultCountPerPath.has(path)) {
|
101 | resultCountPerPath.set(path, resultCountPerPath.get(path) + 1)
|
102 | } else {
|
103 | resultCountPerPath.set(path, 0)
|
104 | }
|
105 |
|
106 | if (isCjsEsmBridgeCov(v8ScriptCov)) {
|
107 | possibleCjsEsmBridges.set(converter, {
|
108 | path,
|
109 | functions: v8ScriptCov.functions
|
110 | })
|
111 | } else {
|
112 | converter.applyCoverage(v8ScriptCov.functions)
|
113 | map.merge(converter.toIstanbul())
|
114 | }
|
115 | } catch (err) {
|
116 | debuglog(`file: ${v8ScriptCov.url} error: ${err.stack}`)
|
117 | }
|
118 | }
|
119 |
|
120 | for (const [converter, { path, functions }] of possibleCjsEsmBridges) {
|
121 | if (resultCountPerPath.get(path) <= 1) {
|
122 | converter.applyCoverage(functions)
|
123 | map.merge(converter.toIstanbul())
|
124 | }
|
125 | }
|
126 | this._allCoverageFiles = map
|
127 | return this._allCoverageFiles
|
128 | }
|
129 |
|
130 | |
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 | _getSourceMap (v8ScriptCov) {
|
141 | const sources = {}
|
142 | const sourceMapAndLineLengths = this.sourceMapCache[pathToFileURL(v8ScriptCov.url).href]
|
143 | if (sourceMapAndLineLengths) {
|
144 |
|
145 | if (!sourceMapAndLineLengths.data) return
|
146 | sources.sourceMap = {
|
147 | sourcemap: sourceMapAndLineLengths.data
|
148 | }
|
149 | if (sourceMapAndLineLengths.lineLengths) {
|
150 | let source = ''
|
151 | sourceMapAndLineLengths.lineLengths.forEach(length => {
|
152 | source += `${''.padEnd(length, '.')}\n`
|
153 | })
|
154 | sources.source = source
|
155 | }
|
156 | }
|
157 | return sources
|
158 | }
|
159 |
|
160 | |
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 | _getMergedProcessCov () {
|
170 | const { mergeProcessCovs } = require('@bcoe/v8-coverage')
|
171 | const v8ProcessCovs = []
|
172 | const fileIndex = new Set()
|
173 | for (const v8ProcessCov of this._loadReports()) {
|
174 | if (this._isCoverageObject(v8ProcessCov)) {
|
175 | if (v8ProcessCov['source-map-cache']) {
|
176 | Object.assign(this.sourceMapCache, this._normalizeSourceMapCache(v8ProcessCov['source-map-cache']))
|
177 | }
|
178 | v8ProcessCovs.push(this._normalizeProcessCov(v8ProcessCov, fileIndex))
|
179 | }
|
180 | }
|
181 |
|
182 | if (this.all) {
|
183 | const emptyReports = []
|
184 | v8ProcessCovs.unshift({
|
185 | result: emptyReports
|
186 | })
|
187 | const workingDirs = this.src
|
188 | for (const workingDir of workingDirs) {
|
189 | this.exclude.globSync(workingDir).forEach((f) => {
|
190 | const fullPath = resolve(workingDir, f)
|
191 | if (!fileIndex.has(fullPath)) {
|
192 | const ext = extname(fullPath)
|
193 | if (ext === '.js' || ext === '.ts' || ext === '.mjs') {
|
194 | const stat = statSync(fullPath)
|
195 | const sourceMap = getSourceMapFromFile(fullPath)
|
196 | if (sourceMap) {
|
197 | this.sourceMapCache[pathToFileURL(fullPath)] = { data: sourceMap }
|
198 | }
|
199 | emptyReports.push({
|
200 | scriptId: 0,
|
201 | url: resolve(fullPath),
|
202 | functions: [{
|
203 | functionName: '(empty-report)',
|
204 | ranges: [{
|
205 | startOffset: 0,
|
206 | endOffset: stat.size,
|
207 | count: 0
|
208 | }],
|
209 | isBlockCoverage: true
|
210 | }]
|
211 | })
|
212 | }
|
213 | }
|
214 | })
|
215 | }
|
216 | }
|
217 |
|
218 | return mergeProcessCovs(v8ProcessCovs)
|
219 | }
|
220 |
|
221 | |
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 | _isCoverageObject (maybeV8ProcessCov) {
|
228 | return maybeV8ProcessCov && Array.isArray(maybeV8ProcessCov.result)
|
229 | }
|
230 |
|
231 | |
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 | _loadReports () {
|
238 | const reports = []
|
239 | for (const file of readdirSync(this.tempDirectory)) {
|
240 | try {
|
241 | reports.push(JSON.parse(readFileSync(
|
242 | resolve(this.tempDirectory, file),
|
243 | 'utf8'
|
244 | )))
|
245 | } catch (err) {
|
246 | debuglog(`${err.stack}`)
|
247 | }
|
248 | }
|
249 | return reports
|
250 | }
|
251 |
|
252 | |
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 | _normalizeProcessCov (v8ProcessCov, fileIndex) {
|
269 | const result = []
|
270 | for (const v8ScriptCov of v8ProcessCov.result) {
|
271 |
|
272 |
|
273 | if (/^node:/.test(v8ScriptCov.url)) {
|
274 | v8ScriptCov.url = `${v8ScriptCov.url.replace(/^node:/, '')}.js`
|
275 | }
|
276 | if (/^file:\/\//.test(v8ScriptCov.url)) {
|
277 | try {
|
278 | v8ScriptCov.url = fileURLToPath(v8ScriptCov.url)
|
279 | fileIndex.add(v8ScriptCov.url)
|
280 | } catch (err) {
|
281 | debuglog(`${err.stack}`)
|
282 | continue
|
283 | }
|
284 | }
|
285 | if ((!this.omitRelative || isAbsolute(v8ScriptCov.url))) {
|
286 | if (this.excludeAfterRemap || this.exclude.shouldInstrument(v8ScriptCov.url)) {
|
287 | result.push(v8ScriptCov)
|
288 | }
|
289 | }
|
290 | }
|
291 | return { result }
|
292 | }
|
293 |
|
294 | |
295 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 |
|
302 |
|
303 | _normalizeSourceMapCache (v8SourceMapCache) {
|
304 | const cache = {}
|
305 | for (const fileURL of Object.keys(v8SourceMapCache)) {
|
306 | cache[pathToFileURL(fileURLToPath(fileURL)).href] = v8SourceMapCache[fileURL]
|
307 | }
|
308 | return cache
|
309 | }
|
310 | }
|
311 |
|
312 | module.exports = function (opts) {
|
313 | return new Report(opts)
|
314 | }
|