Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80668 views
1
/*
2
Copyright (c) 2013, Yahoo! Inc. All rights reserved.
3
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
4
*/
5
var path = require('path'),
6
fs = require('fs'),
7
existsSync = fs.existsSync || path.existsSync,
8
CAMEL_PATTERN = /([a-z])([A-Z])/g,
9
YML_PATTERN = /\.ya?ml$/,
10
yaml = require('js-yaml'),
11
defaults = require('./report/common/defaults');
12
13
function defaultConfig(includeBackCompatAttrs) {
14
var ret = {
15
verbose: false,
16
instrumentation: {
17
root: '.',
18
'default-excludes': true,
19
excludes: [],
20
'embed-source': false,
21
variable: '__coverage__',
22
compact: true,
23
'preserve-comments': false,
24
'complete-copy': false,
25
'save-baseline': false,
26
'baseline-file': './coverage/coverage-baseline.json',
27
'include-all-sources': false,
28
'include-pid': false
29
},
30
reporting: {
31
print: 'summary',
32
reports: [ 'lcov' ],
33
dir: './coverage'
34
},
35
hooks: {
36
'hook-run-in-context': false,
37
'post-require-hook': null,
38
'handle-sigint': false
39
},
40
check: {
41
global: {
42
statements: 0,
43
lines: 0,
44
branches: 0,
45
functions: 0,
46
excludes: [] // Currently list of files (root + path). For future, extend to patterns.
47
},
48
each: {
49
statements: 0,
50
lines: 0,
51
branches: 0,
52
functions: 0,
53
excludes: []
54
}
55
}
56
};
57
ret.reporting.watermarks = defaults.watermarks();
58
ret.reporting['report-config'] = defaults.defaultReportConfig();
59
60
if (includeBackCompatAttrs) {
61
ret.instrumentation['preload-sources'] = false;
62
}
63
64
return ret;
65
}
66
67
function dasherize(word) {
68
return word.replace(CAMEL_PATTERN, function (match, lch, uch) {
69
return lch + '-' + uch.toLowerCase();
70
});
71
}
72
function isScalar(v) {
73
if (v === null) { return true; }
74
return v !== undefined && !Array.isArray(v) && typeof v !== 'object';
75
}
76
77
function isObject(v) {
78
return typeof v === 'object' && v !== null && !Array.isArray(v);
79
}
80
81
function mergeObjects(explicit, template) {
82
83
var ret = {};
84
85
Object.keys(template).forEach(function (k) {
86
var v1 = template[k],
87
v2 = explicit[k];
88
89
if (Array.isArray(v1)) {
90
ret[k] = Array.isArray(v2) && v2.length > 0 ? v2 : v1;
91
} else if (isObject(v1)) {
92
v2 = isObject(v2) ? v2 : {};
93
ret[k] = mergeObjects(v2, v1);
94
} else {
95
ret[k] = isScalar(v2) ? v2 : v1;
96
}
97
});
98
return ret;
99
}
100
101
function mergeDefaults(explicit, implicit) {
102
return mergeObjects(explicit || {}, implicit);
103
}
104
105
function addMethods() {
106
var args = Array.prototype.slice.call(arguments),
107
cons = args.shift();
108
109
args.forEach(function (arg) {
110
var method = arg,
111
property = dasherize(arg);
112
cons.prototype[method] = function () {
113
return this.config[property];
114
};
115
});
116
}
117
118
/**
119
* Object that returns instrumentation options
120
* @class InstrumentOptions
121
* @module config
122
* @constructor
123
* @param config the instrumentation part of the config object
124
*/
125
function InstrumentOptions(config) {
126
if (config['preload-sources']) {
127
console.error('The preload-sources option is deprecated, please use include-all-sources instead.');
128
config['include-all-sources'] = config['preload-sources'];
129
}
130
this.config = config;
131
}
132
133
/**
134
* returns if default excludes should be turned on. Used by the `cover` command.
135
* @method defaultExcludes
136
* @return {Boolean} true if default excludes should be turned on
137
*/
138
/**
139
* returns if non-JS files should be copied during instrumentation. Used by the
140
* `instrument` command.
141
* @method completeCopy
142
* @return {Boolean} true if non-JS files should be copied
143
*/
144
/**
145
* returns if the source should be embedded in the instrumented code. Used by the
146
* `instrument` command.
147
* @method embedSource
148
* @return {Boolean} true if the source should be embedded in the instrumented code
149
*/
150
/**
151
* the coverage variable name to use. Used by the `instrument` command.
152
* @method variable
153
* @return {String} the coverage variable name to use
154
*/
155
/**
156
* returns if the output should be compact JS. Used by the `instrument` command.
157
* @method compact
158
* @return {Boolean} true if the output should be compact
159
*/
160
/**
161
* returns if comments should be preserved in the generated JS. Used by the
162
* `cover` and `instrument` commands.
163
* @method preserveComments
164
* @return {Boolean} true if comments should be preserved in the generated JS
165
*/
166
/**
167
* returns if a zero-coverage baseline file should be written as part of
168
* instrumentation. This allows reporting to display numbers for files that have
169
* no tests. Used by the `instrument` command.
170
* @method saveBaseline
171
* @return {Boolean} true if a baseline coverage file should be written.
172
*/
173
/**
174
* Sets the baseline coverage filename. Used by the `instrument` command.
175
* @method baselineFile
176
* @return {String} the name of the baseline coverage file.
177
*/
178
/**
179
* returns if the coverage filename should include the PID. Used by the `instrument` command.
180
* @method includePid
181
* @return {Boolean} true to include pid in coverage filename.
182
*/
183
184
185
addMethods(InstrumentOptions,
186
'defaultExcludes', 'completeCopy',
187
'embedSource', 'variable', 'compact', 'preserveComments',
188
'saveBaseline', 'baselineFile',
189
'includeAllSources', 'includePid');
190
191
/**
192
* returns the root directory used by istanbul which is typically the root of the
193
* source tree. Used by the `cover` and `report` commands.
194
* @method root
195
* @return {String} the root directory used by istanbul.
196
*/
197
InstrumentOptions.prototype.root = function () { return path.resolve(this.config.root); };
198
/**
199
* returns an array of fileset patterns that should be excluded for instrumentation.
200
* Used by the `instrument` and `cover` commands.
201
* @method excludes
202
* @return {Array} an array of fileset patterns that should be excluded for
203
* instrumentation.
204
*/
205
InstrumentOptions.prototype.excludes = function (excludeTests) {
206
var defs;
207
if (this.defaultExcludes()) {
208
defs = [ '**/node_modules/**' ];
209
if (excludeTests) {
210
defs = defs.concat(['**/test/**', '**/tests/**']);
211
}
212
return defs.concat(this.config.excludes);
213
}
214
return this.config.excludes;
215
};
216
217
/**
218
* Object that returns reporting options
219
* @class ReportingOptions
220
* @module config
221
* @constructor
222
* @param config the reporting part of the config object
223
*/
224
function ReportingOptions(config) {
225
this.config = config;
226
}
227
228
/**
229
* returns the kind of information to be printed on the console. May be one
230
* of `summary`, `detail`, `both` or `none`. Used by the
231
* `cover` command.
232
* @method print
233
* @return {String} the kind of information to print to the console at the end
234
* of the `cover` command execution.
235
*/
236
/**
237
* returns a list of reports that should be generated at the end of a run. Used
238
* by the `cover` and `report` commands.
239
* @method reports
240
* @return {Array} an array of reports that should be produced
241
*/
242
/**
243
* returns the directory under which reports should be generated. Used by the
244
* `cover` and `report` commands.
245
*
246
* @method dir
247
* @return {String} the directory under which reports should be generated.
248
*/
249
/**
250
* returns an object that has keys that are report format names and values that are objects
251
* containing detailed configuration for each format. Running `istanbul help config`
252
* will give you all the keys per report format that can be overridden.
253
* Used by the `cover` and `report` commands.
254
* @method reportConfig
255
* @return {Object} detailed report configuration per report format.
256
*/
257
addMethods(ReportingOptions, 'print', 'reports', 'dir', 'reportConfig');
258
259
function isInvalidMark(v, key) {
260
var prefix = 'Watermark for [' + key + '] :';
261
262
if (v.length !== 2) {
263
return prefix + 'must be an array of length 2';
264
}
265
v[0] = Number(v[0]);
266
v[1] = Number(v[1]);
267
268
if (isNaN(v[0]) || isNaN(v[1])) {
269
return prefix + 'must have valid numbers';
270
}
271
if (v[0] < 0 || v[1] < 0) {
272
return prefix + 'must be positive numbers';
273
}
274
if (v[1] > 100) {
275
return prefix + 'cannot exceed 100';
276
}
277
if (v[1] <= v[0]) {
278
return prefix + 'low must be less than high';
279
}
280
return null;
281
}
282
283
/**
284
* returns the low and high watermarks to be used to designate whether coverage
285
* is `low`, `medium` or `high`. Statements, functions, branches and lines can
286
* have independent watermarks. These are respected by all reports
287
* that color for low, medium and high coverage. See the default configuration for exact syntax
288
* using `istanbul help config`. Used by the `cover` and `report` commands.
289
*
290
* @method watermarks
291
* @return {Object} an object containing low and high watermarks for statements,
292
* branches, functions and lines.
293
*/
294
ReportingOptions.prototype.watermarks = function () {
295
var v = this.config.watermarks,
296
defs = defaults.watermarks(),
297
ret = {};
298
299
Object.keys(defs).forEach(function (k) {
300
var mark = v[k], //it will already be a non-zero length array because of the way the merge works
301
message = isInvalidMark(mark, k);
302
if (message) {
303
console.error(message);
304
ret[k] = defs[k];
305
} else {
306
ret[k] = mark;
307
}
308
});
309
return ret;
310
};
311
312
/**
313
* Object that returns hook options. Note that istanbul does not provide an
314
* option to hook `require`. This is always done by the `cover` command.
315
* @class HookOptions
316
* @module config
317
* @constructor
318
* @param config the hooks part of the config object
319
*/
320
function HookOptions(config) {
321
this.config = config;
322
}
323
324
/**
325
* returns if `vm.runInThisContext` needs to be hooked, in addition to the standard
326
* `require` hooks added by istanbul. This should be true for code that uses
327
* RequireJS for example. Used by the `cover` command.
328
* @method hookRunInContext
329
* @return {Boolean} true if `vm.runInThisContext` needs to be hooked for coverage
330
*/
331
/**
332
* returns a path to JS file or a dependent module that should be used for
333
* post-processing files after they have been required. See the `yui-istanbul` module for
334
* an example of a post-require hook. This particular hook modifies the yui loader when
335
* that file is required to add istanbul interceptors. Use by the `cover` command
336
*
337
* @method postRequireHook
338
* @return {String} a path to a JS file or the name of a node module that needs
339
* to be used as a `require` post-processor
340
*/
341
/**
342
* returns if istanbul needs to add a SIGINT (control-c, usually) handler to
343
* save coverage information. Useful for getting code coverage out of processes
344
* that run forever and need a SIGINT to terminate.
345
* @method handleSigint
346
* @return {Boolean} true if SIGINT needs to be hooked to write coverage information
347
*/
348
349
addMethods(HookOptions, 'hookRunInContext', 'postRequireHook', 'handleSigint');
350
351
/**
352
* represents the istanbul configuration and provides sub-objects that can
353
* return instrumentation, reporting and hook options respectively.
354
* Usage
355
* -----
356
*
357
* var configObj = require('istanbul').config.loadFile();
358
*
359
* console.log(configObj.reporting.reports());
360
*
361
* @class Configuration
362
* @module config
363
* @param {Object} obj the base object to use as the configuration
364
* @param {Object} overrides optional - override attributes that are merged into
365
* the base config
366
* @constructor
367
*/
368
function Configuration(obj, overrides) {
369
370
var config = mergeDefaults(obj, defaultConfig(true));
371
if (isObject(overrides)) {
372
config = mergeDefaults(overrides, config);
373
}
374
if (config.verbose) {
375
console.error('Using configuration');
376
console.error('-------------------');
377
console.error(yaml.safeDump(config, { indent: 4, flowLevel: 3 }));
378
console.error('-------------------\n');
379
}
380
this.verbose = config.verbose;
381
this.instrumentation = new InstrumentOptions(config.instrumentation);
382
this.reporting = new ReportingOptions(config.reporting);
383
this.hooks = new HookOptions(config.hooks);
384
this.check = config.check; // Pass raw config sub-object.
385
}
386
387
/**
388
* true if verbose logging is required
389
* @property verbose
390
* @type Boolean
391
*/
392
/**
393
* instrumentation options
394
* @property instrumentation
395
* @type InstrumentOptions
396
*/
397
/**
398
* reporting options
399
* @property reporting
400
* @type ReportingOptions
401
*/
402
/**
403
* hook options
404
* @property hooks
405
* @type HookOptions
406
*/
407
408
409
function loadFile(file, overrides) {
410
var defaultConfigFile = path.resolve('.istanbul.yml'),
411
configObject;
412
413
if (file) {
414
if (!existsSync(file)) {
415
throw new Error('Invalid configuration file specified:' + file);
416
}
417
} else {
418
if (existsSync(defaultConfigFile)) {
419
file = defaultConfigFile;
420
}
421
}
422
423
if (file) {
424
console.error('Loading config: ' + file);
425
configObject = file.match(YML_PATTERN) ?
426
yaml.safeLoad(fs.readFileSync(file, 'utf8'), { filename: file }) :
427
require(path.resolve(file));
428
}
429
430
return new Configuration(configObject, overrides);
431
}
432
433
function loadObject(obj, overrides) {
434
return new Configuration(obj, overrides);
435
}
436
437
/**
438
* methods to load the configuration object.
439
* Usage
440
* -----
441
*
442
* var config = require('istanbul').config,
443
* configObj = config.loadFile();
444
*
445
* console.log(configObj.reporting.reports());
446
*
447
* @class Config
448
* @module main
449
* @static
450
*/
451
module.exports = {
452
/**
453
* loads the specified configuration file with optional overrides. Throws
454
* when a file is specified and it is not found.
455
* @method loadFile
456
* @static
457
* @param {String} file the file to load. If falsy, the default config file, if present, is loaded.
458
* If not a default config is used.
459
* @param {Object} overrides - an object with override keys that are merged into the
460
* config object loaded
461
* @return {Configuration} the config object with overrides applied
462
*/
463
loadFile: loadFile,
464
/**
465
* loads the specified configuration object with optional overrides.
466
* @method loadObject
467
* @static
468
* @param {Object} obj the object to use as the base configuration.
469
* @param {Object} overrides - an object with override keys that are merged into the
470
* config object
471
* @return {Configuration} the config object with overrides applied
472
*/
473
loadObject: loadObject,
474
/**
475
* returns the default configuration object. Note that this is a plain object
476
* and not a `Configuration` instance.
477
* @method defaultConfig
478
* @static
479
* @return {Object} an object that represents the default config
480
*/
481
defaultConfig: defaultConfig
482
};
483
484