Skip to content

Commit f40d6af

Browse files
pr1smjohnjbarton
authored andcommitted
chore(deps): Use latest istanbul lib packages (#377)
BREAKING CHANGE: This set of changes may impact some use cases. * chore: Add Updated Istanbul Dependencies The istanbul package is deprecated in favor several split packages that control different aspects of how istanbul works. This commit adds the recommended packages that will be used in future commits as karma-coverage's usage of istanbul is updated to the latest api. * refactor(reporter): Follow new report API This commit refactors the in memory report implementation to use the new istanbul report API. Report creation is removed from newer versions of the istanbul API, so this commit adds a set of utility functions to wrap around the new API and provide similar functionality as the old API. The top level export uses the new utility function to register the in-memory report. * refactor(preprocessor): Switch to istanbul-lib-instrument This commit updates the preprocessor to use istanbul-lib-instrument instead of the deprecated istanbul package. The biggest change in this refactor is using a callable function instead of a constructor when creating instrumenters The old istanbul package exposed the Instrumenter directly, allowing the preprocessor to create an instance of it. istanbul-lib-instrument, however, exposes a callable function that creates an Instrumenter. This commit updates the preprocessor to follow this new pattern of using a callable function. In order to ensure backwards compatibility, a utility function is added to wrap constructors with a callable function for creation automatically. This change allows the following configuration for creating instrumenters: 1. An object that contains an Instrumenter constructor 2. An Instrumenter constructor itself 3. A callable function that returns an Instrumenter instance. This commit also uses the istanbul-lib-source-maps package to handle storing source maps. A global source map store registers source maps so they can be used later on in the reporter. * refactor(reporter): Switch to istanbul-lib-coverage This commit updates the reporter by using the istanbul-lib-coverage package api for handling coverage checking/management and the istanbul-lib-report package api for handling reporting. The new apis remove the need for collectors and remove the need to handle disposing collectors. * refactor: Remove unused source cache utilities This commit removes the source-cache-store and source-cache files as they are no longer being used. The source-map-store and istanbul-lib-source-maps are used instead, so these files are no longer needed. * feat(util): Add Reset Functionality This commit updates the report creator utility to allow resetting the custom reporter map. * fix(preprocessor): Track Coverage Maps Properly This commit updates the preprocessor to properly access file coverage when storing it in the global coverage map (when includeAllSources is true). The previous method did not work because the returned instrumented code from the default istanbul instrumenter returns the coverage map in a POJO object instead of JSON notation. This breaks the coverage regex used to match and parse the coverage map. The istanbul instrumenter offers the ability to receive the coverage map for the last instrumented file through a separate function, so that is tested for and used if it is supported. The original method is used as a fallback for backwards compatibility. This commit also addresses changes from the v0 instanbul instrumenter options. The changes are additive only to maintain backwards compatibility for other instrumenters. * fix(reporter): Access Data Properly to Check Coverage This commit fixes errors with accessing data properly during the checkCoverage method. A previous commit updated the implementation to use istanbul-lib-coverage, but this involved an api change to access the raw coverage data (which checkCoverage uses). This commit also fixes the checking coverage for each file by using a map to store file coverage summaries instead of merging summaries like the global results. Per file coverage now works as expected. * test: Update Unit Tests to use new Istanbul API This commit updates the mocking done in unit tests to properly mock the new istanbul API. Additionally, new unit test suites are added for the utility methods report-creator and source-map-store.
1 parent b8f82a0 commit f40d6af

14 files changed

+440
-339
lines changed

Diff for: lib/in-memory-report.js

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
var Report = require('istanbul').Report
2-
var util = require('util')
3-
41
function InMemoryReport (opt) {
52
this.opt = opt
63
}
74

8-
util.inherits(InMemoryReport, Report)
5+
InMemoryReport.prototype.onStart = function (root, context) {
6+
this.data = {}
7+
}
8+
9+
InMemoryReport.prototype.onDetail = function (node) {
10+
const fc = node.getFileCoverage()
11+
const key = fc.path
12+
this.data[key] = fc.toJSON()
13+
}
914

10-
InMemoryReport.prototype.writeReport = function (collector, sync) {
15+
InMemoryReport.prototype.onEnd = function () {
1116
if (!this.opt.emitter || !this.opt.emitter.emit) {
1217
console.error('Could not raise "coverage_complete" event, missing emitter because it was not supplied during initialization of the reporter')
1318
} else {
14-
this.opt.emitter.emit('coverage_complete', this.opt.browser, collector.getFinalCoverage())
19+
this.opt.emitter.emit('coverage_complete', this.opt.browser, this.data)
1520
}
1621
}
1722

Diff for: lib/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Exposes the preprocessor and reporter plugins.
66

77
// Registering one additional (karma specific) reporter: in-memory
8-
require('istanbul').Report.register(require('./in-memory-report'))
8+
require('./report-creator').register(require('./in-memory-report'))
99

1010
module.exports = {
1111
'preprocessor:coverage': ['factory', require('./preprocessor')],

Diff for: lib/preprocessor.js

+68-21
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
// Dependencies
77
// ------------
88

9-
var istanbul = require('istanbul')
9+
var { createInstrumenter } = require('istanbul-lib-instrument')
1010
var minimatch = require('minimatch')
1111
var path = require('path')
1212
var _ = require('lodash')
1313
var SourceMapConsumer = require('source-map').SourceMapConsumer
1414
var SourceMapGenerator = require('source-map').SourceMapGenerator
15-
var globalSourceCache = require('./source-cache')
16-
var coverageMap = require('./coverage-map')
15+
var globalSourceMapStore = require('./source-map-store')
16+
var globalCoverageMap = require('./coverage-map')
1717

1818
// Regexes
1919
// -------
@@ -27,21 +27,60 @@ function createCoveragePreprocessor (logger, helper, basePath, reporters, covera
2727
// Options
2828
// -------
2929

30+
function isConstructor (Func) {
31+
try {
32+
// eslint-disable-next-line
33+
new Func()
34+
} catch (err) {
35+
// error message should be of the form: "TypeError: func is not a constructor"
36+
// test for this type of message to ensure we failed due to the function not being
37+
// constructable
38+
if (/TypeError.*constructor/.test(err.message)) {
39+
return false
40+
}
41+
}
42+
return true
43+
}
44+
45+
function getCreatorFunction (Obj) {
46+
if (Obj.Instrumenter) {
47+
return function (opts) {
48+
return new Obj.Instrumenter(opts)
49+
}
50+
}
51+
if (!_.isFunction(Obj)) {
52+
// Object doesn't have old instrumenter variable and isn't a
53+
// constructor, so we can't use it to create an instrumenter
54+
return null
55+
}
56+
if (isConstructor(Obj)) {
57+
return function (opts) {
58+
return new Obj(opts)
59+
}
60+
}
61+
return Obj
62+
}
63+
3064
var instrumenterOverrides = {}
31-
var instrumenters = { istanbul: istanbul }
65+
var instrumenters = { istanbul: createInstrumenter }
3266
var includeAllSources = false
3367
var useJSExtensionForCoffeeScript = false
3468

3569
if (coverageReporter) {
3670
instrumenterOverrides = coverageReporter.instrumenter
37-
instrumenters = Object.assign({}, { istanbul: istanbul }, coverageReporter.instrumenters)
71+
_.forEach(coverageReporter.instrumenters, function (instrumenter, literal) {
72+
var creatorFunction = getCreatorFunction(instrumenter)
73+
if (creatorFunction) {
74+
instrumenters[literal] = creatorFunction
75+
}
76+
})
3877
includeAllSources = coverageReporter.includeAllSources === true
3978
useJSExtensionForCoffeeScript = coverageReporter.useJSExtensionForCoffeeScript === true
4079
}
4180

42-
var sourceCache = globalSourceCache.get(basePath)
81+
var sourceMapStore = globalSourceMapStore.get(basePath)
4382

44-
var instrumentersOptions = _.reduce(instrumenters, function getInstumenterOptions (memo, instrument, name) {
83+
var instrumentersOptions = _.reduce(instrumenters, function getInstrumenterOptions (memo, instrument, name) {
4584
memo[name] = {}
4685

4786
if (coverageReporter && coverageReporter.instrumenterOptions) {
@@ -88,9 +127,11 @@ function createCoveragePreprocessor (logger, helper, basePath, reporters, covera
88127
}
89128
})
90129

91-
var InstrumenterConstructor = instrumenters[instrumenterLiteral].Instrumenter
130+
var instrumenterCreator = instrumenters[instrumenterLiteral]
92131
var constructOptions = instrumentersOptions[instrumenterLiteral] || {}
132+
var options = Object.assign({}, constructOptions)
93133
var codeGenerationOptions = null
134+
options.autoWrap = options.autoWrap || !options.noAutoWrap
94135

95136
if (file.sourceMap) {
96137
log.debug('Enabling source map generation for "%s".', file.originalPath)
@@ -102,12 +143,12 @@ function createCoveragePreprocessor (logger, helper, basePath, reporters, covera
102143
sourceMapWithCode: true,
103144
file: file.path
104145
}, constructOptions.codeGenerationOptions || {})
146+
options.produceSourceMap = true
105147
}
106148

107-
var options = Object.assign({}, constructOptions)
108149
options = Object.assign({}, options, { codeGenerationOptions: codeGenerationOptions })
109150

110-
var instrumenter = new InstrumenterConstructor(options)
151+
var instrumenter = instrumenterCreator(options)
111152
instrumenter.instrument(content, jsPath, function (err, instrumentedCode) {
112153
if (err) {
113154
log.error('%s\n at %s', err.message, file.originalPath)
@@ -122,19 +163,25 @@ function createCoveragePreprocessor (logger, helper, basePath, reporters, covera
122163
instrumentedCode += Buffer.from(JSON.stringify(file.sourceMap)).toString('base64') + '\n'
123164
}
124165

125-
// remember the actual immediate instrumented JS for given original path
126-
sourceCache[jsPath] = content
166+
// Register the sourceMap for transformation during reporting
167+
sourceMapStore.registerMap(jsPath, file.sourceMap)
127168

128169
if (includeAllSources) {
129-
// reset stateful regex
130-
coverageObjRegex.lastIndex = 0
131-
132-
var coverageObjMatch = coverageObjRegex.exec(instrumentedCode)
133-
134-
if (coverageObjMatch !== null) {
135-
var coverageObj = JSON.parse(coverageObjMatch[0])
136-
137-
coverageMap.add(coverageObj)
170+
var coverageObj
171+
// Check if the file coverage object is exposed from the instrumenter directly
172+
if (instrumenter.lastFileCoverage) {
173+
coverageObj = instrumenter.lastFileCoverage()
174+
globalCoverageMap.add(coverageObj)
175+
} else {
176+
// Attempt to match and parse coverage object from instrumented code
177+
178+
// reset stateful regex
179+
coverageObjRegex.lastIndex = 0
180+
var coverageObjMatch = coverageObjRegex.exec(instrumentedCode)
181+
if (coverageObjMatch !== null) {
182+
coverageObj = JSON.parse(coverageObjMatch[0])
183+
globalCoverageMap.add(coverageObj)
184+
}
138185
}
139186
}
140187

Diff for: lib/report-creator.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Report Creator
2+
// ==============
3+
//
4+
// Wrapper of Istanbul's report creator to allow registering
5+
// custom reporters
6+
7+
// Dependencies
8+
// ------------
9+
var istanbulReports = require('istanbul-reports')
10+
11+
var customReporterMap = {}
12+
13+
function register (reporter) {
14+
var registeredType = reporter.TYPE
15+
if (!registeredType) {
16+
throw new Error('Registering a custom reporter requires a type!')
17+
}
18+
19+
customReporterMap[registeredType] = reporter
20+
return registeredType
21+
}
22+
23+
function create (type, opts) {
24+
var Reporter = customReporterMap[type]
25+
if (Reporter) {
26+
return new Reporter(opts)
27+
}
28+
29+
// fallback to istanbul's report creator if reporter isn't found
30+
return istanbulReports.create(type, opts)
31+
}
32+
33+
function reset () {
34+
customReporterMap = {}
35+
}
36+
37+
module.exports = {
38+
create: create,
39+
register: register,
40+
reset: reset
41+
}

0 commit comments

Comments
 (0)