1 |
|
2 | var fs = require('fs')
|
3 | var glob = require('glob')
|
4 | var micromatch = require('micromatch')
|
5 | var mkdirp = require('mkdirp')
|
6 | var Module = require('module')
|
7 | var appendTransform = require('append-transform')
|
8 | var cachingTransform = require('caching-transform')
|
9 | var path = require('path')
|
10 | var rimraf = require('rimraf')
|
11 | var onExit = require('signal-exit')
|
12 | var resolveFrom = require('resolve-from')
|
13 | var arrify = require('arrify')
|
14 | var SourceMapCache = require('./lib/source-map-cache')
|
15 | var convertSourceMap = require('convert-source-map')
|
16 | var md5hex = require('md5-hex')
|
17 | var findCacheDir = require('find-cache-dir')
|
18 | var js = require('default-require-extensions/js')
|
19 | var pkgUp = require('pkg-up')
|
20 | var yargs = require('yargs/yargs')
|
21 |
|
22 |
|
23 | if (/index\.covered\.js$/.test(__filename)) {
|
24 | require('./lib/self-coverage-helper')
|
25 | }
|
26 |
|
27 | function NYC (opts) {
|
28 | var config = this._loadConfig(opts || {})
|
29 |
|
30 | this._istanbul = config.istanbul
|
31 | this.subprocessBin = config.subprocessBin || path.resolve(__dirname, './bin/nyc.js')
|
32 | this._tempDirectory = config.tempDirectory || './.nyc_output'
|
33 | this._reportDir = config.reportDir
|
34 | this.cwd = config.cwd
|
35 |
|
36 | this.reporter = arrify(config.reporter || 'text')
|
37 |
|
38 |
|
39 | this.include = false
|
40 | if (config.include && config.include.length > 0) {
|
41 | this.include = this._prepGlobPatterns(arrify(config.include))
|
42 | }
|
43 |
|
44 | this.exclude = this._prepGlobPatterns(
|
45 | ['**/node_modules/**'].concat(arrify(
|
46 | config.exclude && config.exclude.length > 0
|
47 | ? config.exclude
|
48 | : ['test/**', 'test{,-*}.js', '**/*.test.js', '**/__tests__/**']
|
49 | ))
|
50 | )
|
51 |
|
52 | this.cacheDirectory = findCacheDir({name: 'nyc', cwd: this.cwd})
|
53 |
|
54 | this.enableCache = Boolean(this.cacheDirectory && (config.enableCache === true || process.env.NYC_CACHE === 'enable'))
|
55 |
|
56 |
|
57 | this.require = arrify(config.require)
|
58 |
|
59 | this.extensions = arrify(config.extension).concat('.js').map(function (ext) {
|
60 | return ext.toLowerCase()
|
61 | })
|
62 |
|
63 | this.transforms = this.extensions.reduce(function (transforms, ext) {
|
64 | transforms[ext] = this._createTransform(ext)
|
65 | return transforms
|
66 | }.bind(this), {})
|
67 |
|
68 | this.sourceMapCache = new SourceMapCache()
|
69 |
|
70 | this.hashCache = {}
|
71 | this.loadedMaps = null
|
72 | }
|
73 |
|
74 | NYC.prototype._loadConfig = function (opts) {
|
75 | var cwd = opts.cwd || process.env.NYC_CWD || process.cwd()
|
76 | var pkgPath = pkgUp.sync(cwd)
|
77 |
|
78 | if (pkgPath) {
|
79 | cwd = path.dirname(pkgPath)
|
80 | }
|
81 |
|
82 | opts.cwd = cwd
|
83 |
|
84 | return yargs([])
|
85 | .pkgConf('nyc', cwd)
|
86 | .default(opts)
|
87 | .argv
|
88 | }
|
89 |
|
90 | NYC.prototype._createTransform = function (ext) {
|
91 | var _this = this
|
92 | return cachingTransform({
|
93 | salt: JSON.stringify({
|
94 | istanbul: require('istanbul/package.json').version,
|
95 | nyc: require('./package.json').version
|
96 | }),
|
97 | hash: function (code, metadata, salt) {
|
98 | var hash = md5hex([code, metadata.filename, salt])
|
99 | _this.hashCache[metadata.filename] = hash
|
100 | return hash
|
101 | },
|
102 | factory: this._transformFactory.bind(this),
|
103 | cacheDir: this.cacheDirectory,
|
104 | disableCache: !this.enableCache,
|
105 | ext: ext
|
106 | })
|
107 | }
|
108 |
|
109 | NYC.prototype._loadAdditionalModules = function () {
|
110 | var _this = this
|
111 | this.require.forEach(function (r) {
|
112 |
|
113 |
|
114 | var p = resolveFrom(_this.cwd, r)
|
115 | if (p) {
|
116 | require(p)
|
117 | return
|
118 | }
|
119 |
|
120 | require(r)
|
121 | })
|
122 | }
|
123 |
|
124 | NYC.prototype.instrumenter = function () {
|
125 | return this._instrumenter || (this._instrumenter = this._createInstrumenter())
|
126 | }
|
127 |
|
128 | NYC.prototype._createInstrumenter = function () {
|
129 | var configFile = path.resolve(this.cwd, './.istanbul.yml')
|
130 |
|
131 | if (!fs.existsSync(configFile)) configFile = undefined
|
132 |
|
133 | var istanbul = this.istanbul()
|
134 |
|
135 | var instrumenterConfig = istanbul.config.loadFile(configFile).instrumentation.config
|
136 |
|
137 | return new istanbul.Instrumenter({
|
138 | coverageVariable: '__coverage__',
|
139 | embedSource: instrumenterConfig['embed-source'],
|
140 | noCompact: !instrumenterConfig.compact,
|
141 | preserveComments: instrumenterConfig['preserve-comments']
|
142 | })
|
143 | }
|
144 |
|
145 | NYC.prototype._prepGlobPatterns = function (patterns) {
|
146 | if (!patterns) return patterns
|
147 |
|
148 | var result = []
|
149 |
|
150 | function add (pattern) {
|
151 | if (result.indexOf(pattern) === -1) {
|
152 | result.push(pattern)
|
153 | }
|
154 | }
|
155 |
|
156 | patterns.forEach(function (pattern) {
|
157 |
|
158 | if (!/\/\*\*$/.test(pattern)) {
|
159 | add(pattern.replace(/\/$/, '') + '/**')
|
160 | }
|
161 |
|
162 | add(pattern)
|
163 | })
|
164 |
|
165 | return result
|
166 | }
|
167 |
|
168 | NYC.prototype.addFile = function (filename) {
|
169 | var relFile = path.relative(this.cwd, filename)
|
170 | var source = this._readTranspiledSource(path.resolve(this.cwd, filename))
|
171 | var instrumentedSource = this._maybeInstrumentSource(source, filename, relFile)
|
172 |
|
173 | return {
|
174 | instrument: !!instrumentedSource,
|
175 | relFile: relFile,
|
176 | content: instrumentedSource || source
|
177 | }
|
178 | }
|
179 |
|
180 | NYC.prototype._readTranspiledSource = function (path) {
|
181 | var source = null
|
182 | Module._extensions['.js']({
|
183 | _compile: function (content, filename) {
|
184 | source = content
|
185 | }
|
186 | }, path)
|
187 | return source
|
188 | }
|
189 |
|
190 | NYC.prototype.shouldInstrumentFile = function (filename, relFile) {
|
191 |
|
192 | if (/^\.\./.test(path.relative(this.cwd, filename))) return false
|
193 |
|
194 | relFile = relFile.replace(/^\.[\\\/]/, '')
|
195 | return (!this.include || micromatch.any(relFile, this.include)) && !micromatch.any(relFile, this.exclude)
|
196 | }
|
197 |
|
198 | NYC.prototype.addAllFiles = function () {
|
199 | var _this = this
|
200 |
|
201 | this._loadAdditionalModules()
|
202 |
|
203 | var pattern = null
|
204 | if (this.extensions.length === 1) {
|
205 | pattern = '**/*' + this.extensions[0]
|
206 | } else {
|
207 | pattern = '**/*{' + this.extensions.join() + '}'
|
208 | }
|
209 |
|
210 | glob.sync(pattern, {cwd: this.cwd, nodir: true, ignore: this.exclude}).forEach(function (filename) {
|
211 | var obj = _this.addFile(path.join(_this.cwd, filename))
|
212 | if (obj.instrument) {
|
213 | module._compile(
|
214 | _this.instrumenter().getPreamble(obj.content, obj.relFile),
|
215 | filename
|
216 | )
|
217 | }
|
218 | })
|
219 |
|
220 | this.writeCoverageFile()
|
221 | }
|
222 |
|
223 | NYC.prototype._maybeInstrumentSource = function (code, filename, relFile) {
|
224 | var instrument = this.shouldInstrumentFile(filename, relFile)
|
225 |
|
226 | if (!instrument) {
|
227 | return null
|
228 | }
|
229 |
|
230 | var ext, transform
|
231 | for (ext in this.transforms) {
|
232 | if (filename.toLowerCase().substr(-ext.length) === ext) {
|
233 | transform = this.transforms[ext]
|
234 | break
|
235 | }
|
236 | }
|
237 |
|
238 | return transform ? transform(code, {filename: filename, relFile: relFile}) : null
|
239 | }
|
240 |
|
241 | NYC.prototype._transformFactory = function (cacheDir) {
|
242 | var _this = this
|
243 | var instrumenter = this.instrumenter()
|
244 |
|
245 | return function (code, metadata, hash) {
|
246 | var filename = metadata.filename
|
247 |
|
248 | var sourceMap = convertSourceMap.fromSource(code) || convertSourceMap.fromMapFileSource(code, path.dirname(filename))
|
249 | if (sourceMap) {
|
250 | if (hash) {
|
251 | var mapPath = path.join(cacheDir, hash + '.map')
|
252 | fs.writeFileSync(mapPath, sourceMap.toJSON())
|
253 | } else {
|
254 | _this.sourceMapCache.addMap(filename, sourceMap.toJSON())
|
255 | }
|
256 | }
|
257 |
|
258 | return instrumenter.instrumentSync(code, filename)
|
259 | }
|
260 | }
|
261 |
|
262 | NYC.prototype._handleJs = function (code, filename) {
|
263 | var relFile = path.relative(this.cwd, filename)
|
264 | return this._maybeInstrumentSource(code, filename, relFile) || code
|
265 | }
|
266 |
|
267 | NYC.prototype._wrapRequire = function () {
|
268 | var handleJs = this._handleJs.bind(this)
|
269 |
|
270 | this.extensions.forEach(function (ext) {
|
271 | require.extensions[ext] = js
|
272 | appendTransform(handleJs, ext)
|
273 | })
|
274 | }
|
275 |
|
276 | NYC.prototype.cleanup = function () {
|
277 | if (!process.env.NYC_CWD) rimraf.sync(this.tempDirectory())
|
278 | }
|
279 |
|
280 | NYC.prototype.clearCache = function () {
|
281 | if (this.enableCache) {
|
282 | rimraf.sync(this.cacheDirectory)
|
283 | }
|
284 | }
|
285 |
|
286 | NYC.prototype.createTempDirectory = function () {
|
287 | mkdirp.sync(this.tempDirectory())
|
288 | }
|
289 |
|
290 | NYC.prototype.reset = function () {
|
291 | this.cleanup()
|
292 | this.createTempDirectory()
|
293 | }
|
294 |
|
295 | NYC.prototype._wrapExit = function () {
|
296 | var _this = this
|
297 |
|
298 |
|
299 |
|
300 | onExit(function () {
|
301 | _this.writeCoverageFile()
|
302 | }, {alwaysLast: true})
|
303 | }
|
304 |
|
305 | NYC.prototype.wrap = function (bin) {
|
306 | this._wrapRequire()
|
307 | this._wrapExit()
|
308 | this._loadAdditionalModules()
|
309 | return this
|
310 | }
|
311 |
|
312 | NYC.prototype.writeCoverageFile = function () {
|
313 | var coverage = global.__coverage__
|
314 | if (typeof __coverage__ === 'object') coverage = __coverage__
|
315 | if (!coverage) return
|
316 |
|
317 | if (this.enableCache) {
|
318 | Object.keys(coverage).forEach(function (absFile) {
|
319 | if (this.hashCache[absFile] && coverage[absFile]) {
|
320 | coverage[absFile].contentHash = this.hashCache[absFile]
|
321 | }
|
322 | }, this)
|
323 | } else {
|
324 | this.sourceMapCache.applySourceMaps(coverage)
|
325 | }
|
326 |
|
327 | fs.writeFileSync(
|
328 | path.resolve(this.tempDirectory(), './', process.pid + '.json'),
|
329 | JSON.stringify(coverage),
|
330 | 'utf-8'
|
331 | )
|
332 | }
|
333 |
|
334 | NYC.prototype.istanbul = function () {
|
335 | return this._istanbul || (this._istanbul = require('istanbul'))
|
336 | }
|
337 |
|
338 | NYC.prototype.report = function (cb, _collector, _reporter) {
|
339 | cb = cb || function () {}
|
340 |
|
341 | var istanbul = this.istanbul()
|
342 | var collector = _collector || new istanbul.Collector()
|
343 | var reporter = _reporter || new istanbul.Reporter(null, this._reportDir)
|
344 |
|
345 | this._loadReports().forEach(function (report) {
|
346 | collector.add(report)
|
347 | })
|
348 |
|
349 | this.reporter.forEach(function (_reporter) {
|
350 | reporter.add(_reporter)
|
351 | })
|
352 |
|
353 | reporter.write(collector, true, cb)
|
354 | }
|
355 |
|
356 | NYC.prototype._loadReports = function () {
|
357 | var _this = this
|
358 | var files = fs.readdirSync(this.tempDirectory())
|
359 |
|
360 | var cacheDir = _this.cacheDirectory
|
361 |
|
362 | var loadedMaps = this.loadedMaps || (this.loadedMaps = {})
|
363 |
|
364 | return files.map(function (f) {
|
365 | var report
|
366 | try {
|
367 | report = JSON.parse(fs.readFileSync(
|
368 | path.resolve(_this.tempDirectory(), './', f),
|
369 | 'utf-8'
|
370 | ))
|
371 | } catch (e) {
|
372 | return {}
|
373 | }
|
374 |
|
375 | Object.keys(report).forEach(function (absFile) {
|
376 | var fileReport = report[absFile]
|
377 | if (fileReport && fileReport.contentHash) {
|
378 | var hash = fileReport.contentHash
|
379 | if (!(hash in loadedMaps)) {
|
380 | try {
|
381 | var mapPath = path.join(cacheDir, hash + '.map')
|
382 | loadedMaps[hash] = JSON.parse(fs.readFileSync(mapPath, 'utf8'))
|
383 | } catch (e) {
|
384 |
|
385 | loadedMaps[hash] = false
|
386 | }
|
387 | }
|
388 | if (loadedMaps[hash]) {
|
389 | _this.sourceMapCache.addMap(absFile, loadedMaps[hash])
|
390 | }
|
391 | }
|
392 | })
|
393 | _this.sourceMapCache.applySourceMaps(report)
|
394 | return report
|
395 | })
|
396 | }
|
397 |
|
398 | NYC.prototype.tempDirectory = function () {
|
399 | return path.resolve(this.cwd, './', this._tempDirectory)
|
400 | }
|
401 |
|
402 | NYC.prototype.mungeArgs = function (yargv) {
|
403 | var argv = process.argv.slice(1)
|
404 | argv = argv.slice(argv.indexOf(yargv._[0]))
|
405 | return argv
|
406 | }
|
407 |
|
408 | module.exports = NYC
|