monocart-coverage-reports 2.5.4 → 2.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -65,8 +65,8 @@ console.log(coverageResults.summary);
65
65
  ## Available Reports
66
66
  > V8 build-in reports (V8 data only):
67
67
  - `v8`
68
- - Browser: Build with webpack [V8](https://cenfun.github.io/monocart-coverage-reports/v8) and [V8 Minify](https://cenfun.github.io/monocart-coverage-reports/v8-minify); Build with [Rollup](https://cenfun.github.io/monocart-coverage-reports/v8-rollup) and [Esbuild](https://cenfun.github.io/monocart-coverage-reports/v8-esbuild); Collect with [puppeteer](https://cenfun.github.io/monocart-coverage-reports/puppeteer/); [anonymous](https://cenfun.github.io/monocart-coverage-reports/anonymous/) and [css](https://cenfun.github.io/monocart-coverage-reports/css/)
69
- - Node.js: Collect with [env](https://cenfun.github.io/monocart-coverage-reports/v8-node-env), and also V8 [API](https://cenfun.github.io/monocart-coverage-reports/v8-node-api), [Inspector](https://cenfun.github.io/monocart-coverage-reports/v8-node-ins) and [CDP](https://cenfun.github.io/monocart-coverage-reports/v8-node-cdp); Web server example: [koa](https://cenfun.github.io/monocart-coverage-reports/v8-node-koa/)
68
+ - Browser: Build with webpack [V8](https://cenfun.github.io/monocart-coverage-reports/v8) and [Minify](https://cenfun.github.io/monocart-coverage-reports/minify); Build with [Rollup](https://cenfun.github.io/monocart-coverage-reports/rollup) and [Esbuild](https://cenfun.github.io/monocart-coverage-reports/esbuild); Collect with [puppeteer](https://cenfun.github.io/monocart-coverage-reports/puppeteer/); [anonymous](https://cenfun.github.io/monocart-coverage-reports/anonymous/) and [css](https://cenfun.github.io/monocart-coverage-reports/css/)
69
+ - Node.js: Collect with [env](https://cenfun.github.io/monocart-coverage-reports/node-env), and also V8 [API](https://cenfun.github.io/monocart-coverage-reports/node-api), [Inspector](https://cenfun.github.io/monocart-coverage-reports/node-ins) and [CDP](https://cenfun.github.io/monocart-coverage-reports/node-cdp); Web server example: [koa](https://cenfun.github.io/monocart-coverage-reports/node-koa/)
70
70
 
71
71
  ![](./assets/v8.gif)
72
72
 
@@ -125,7 +125,7 @@ console.log(coverageResults.summary);
125
125
  }
126
126
  ```
127
127
  - istanbul custom reporter
128
- > example: [./test/custom-istanbul-reporter.js](./test/custom-istanbul-reporter.js), see [istanbul built-in reporters' implementation](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib) for reference,
128
+ > example: [./test/custom-istanbul-reporter.js](./test/custom-istanbul-reporter.js), see [istanbul built-in reporters' implementation](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib) for reference.
129
129
  - v8 custom reporter
130
130
  > example: [./test/custom-v8-reporter.js](./test/custom-v8-reporter.js)
131
131
 
@@ -183,13 +183,20 @@ We can use `sourceFilter` to filter the source files. For example, we should rem
183
183
  > src/index.js
184
184
  > src/components/app.js
185
185
  ```
186
- Example:
186
+ For example:
187
187
  ```js
188
188
  const coverageOptions = {
189
189
  entryFilter: (entry) => entry.url.indexOf("main.js") !== -1,
190
190
  sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
191
191
  };
192
192
  ```
193
+ Or `minimatch` pattern:
194
+ ```js
195
+ const coverageOptions = {
196
+ entryFilter: "**/main.js",
197
+ sourceFilter: "**/src/**"
198
+ };
199
+ ```
193
200
 
194
201
  ## onEnd Hook
195
202
  For example, checking thresholds:
@@ -245,19 +252,29 @@ Arguments:
245
252
 
246
253
  Options:
247
254
  -V, --version output the version number
248
- -c, --config <path> config path for options
255
+ -c, --config <path> custom config path
249
256
  -o, --outputDir <dir> output dir for reports
250
257
  -r, --reports <name[,name]> coverage reports to use
251
258
  -n, --name <name> report name for title
259
+ -i, --inputDir <dir> input dir for merging raw files
260
+ --entryFilter <pattern> entry url filter
261
+ --sourceFilter <pattern> source path filter
252
262
  --outputFile <path> output file for v8 report
253
263
  --inline inline html for v8 report
254
264
  --assetsPath <path> assets path if not inline
255
265
  --lcov generate lcov.info file
266
+ --logging <logging> off, error, info, debug
256
267
  -h, --help display help for command
257
268
  ```
258
- - Using configuration file for more [options](#default-options)
269
+ - Supports loading default configuration file
270
+ - `.mcrrc`
271
+ - `mcr.config.json`
272
+ - `mcr.config.mjs`
273
+ - `mcr.config.cjs`
274
+ - `mcr.config.js`
275
+ - Specify custom configuration file
259
276
  ```sh
260
- mcr "node ./test/test-node-env.js" -c test/cli-options.js
277
+ mcr "node ./test.js" -c path-to/my-custom-config.js
261
278
  ```
262
279
 
263
280
  ## Compare Reports
package/lib/cli.js CHANGED
@@ -3,8 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const EC = require('eight-colors');
6
- const { fileURLToPath } = require('url');
7
-
6
+ const { fileURLToPath, pathToFileURL } = require('url');
8
7
  const {
9
8
  foregroundChild,
10
9
  program
@@ -13,21 +12,76 @@ const {
13
12
  const MCR = require('./index.js');
14
13
  const Util = require('./utils/util.js');
15
14
  const defaultOptions = require('./default/options.js');
15
+ const { resolveSourceMap } = require('./converter/collect-source-maps.js');
16
+
16
17
  const version = require('../package.json').version;
17
18
 
18
19
  const cleanDir = (dir) => {
19
20
  Util.rmSync(dir);
20
21
  };
21
22
 
22
- const initOptions = (cliOptions) => {
23
- // merge all cli options
24
- const configPath = cliOptions.config;
23
+ const resolveConfigPath = (customConfigPath) => {
24
+
25
+ const configList = [
26
+ '.mcrrc',
27
+ 'mcr.config.json',
28
+ 'mcr.config.mjs',
29
+ 'mcr.config.cjs',
30
+ 'mcr.config.js'
31
+ ];
32
+
33
+ if (customConfigPath) {
34
+ configList.push(customConfigPath);
35
+ }
36
+
37
+ const existsList = configList.filter((p) => fs.existsSync(p));
38
+ if (!existsList.length) {
39
+ return;
40
+ }
41
+
42
+ // last one
43
+ const configPath = existsList.pop();
44
+ Util.logInfo(`Config: ${EC.cyan(configPath)}`);
45
+
46
+ return configPath;
47
+ };
48
+
49
+ const resolveConfigOptions = async (configPath) => {
50
+
51
+ // json format
52
+ const ext = path.extname(configPath);
53
+ if (ext === '.json' || configPath.slice(-2) === 'rc') {
54
+ return JSON.parse(Util.readFileSync(configPath));
55
+ }
56
+
57
+ let configOptions;
58
+ let err;
59
+ try {
60
+ configOptions = await import(pathToFileURL(configPath));
61
+ } catch (ee) {
62
+ err = ee;
63
+ }
64
+
65
+ if (!configOptions) {
66
+ Util.logError(err.message);
67
+ return;
68
+ }
69
+
70
+ configOptions = configOptions.default || configOptions;
71
+
72
+ return configOptions;
73
+ };
74
+
75
+ const initOptions = async (cliOptions) => {
76
+
77
+ const configPath = resolveConfigPath(cliOptions.config);
25
78
  if (configPath) {
26
- if (fs.existsSync(configPath)) {
27
- const config = require(path.resolve(configPath));
28
- Object.assign(cliOptions, config);
29
- } else {
30
- EC.logRed(`Not found config: ${configPath}`);
79
+ const configOptions = await resolveConfigOptions(configPath);
80
+ if (configOptions) {
81
+ cliOptions = {
82
+ ... configOptions,
83
+ ... cliOptions
84
+ };
31
85
  }
32
86
  }
33
87
 
@@ -46,9 +100,9 @@ const initOptions = (cliOptions) => {
46
100
 
47
101
  const executeCommand = async (command, cliOptions) => {
48
102
 
49
- const options = initOptions(cliOptions);
103
+ const options = await initOptions(cliOptions);
50
104
 
51
- console.log('Execute:', EC.cyan(command));
105
+ Util.logInfo(`Execute: ${EC.cyan(command)}`);
52
106
  // console.log(options);
53
107
 
54
108
  const nodeV8CoverageDir = path.resolve(options.outputDir, '.v8-coverage');
@@ -82,6 +136,9 @@ const executeCommand = async (command, cliOptions) => {
82
136
  const json = JSON.parse(content);
83
137
  let coverageList = json.result;
84
138
 
139
+ // for node sourcemap
140
+ const sourceMapCache = json['source-map-cache'] || {};
141
+
85
142
  // filter node internal files
86
143
  coverageList = coverageList.filter((entry) => entry.url && entry.url.startsWith('file:'));
87
144
 
@@ -90,11 +147,28 @@ const executeCommand = async (command, cliOptions) => {
90
147
  }
91
148
  // attached source content
92
149
  coverageList.forEach((entry) => {
150
+ // source content
93
151
  const filePath = fileURLToPath(entry.url);
94
152
  if (fs.existsSync(filePath)) {
95
153
  entry.source = fs.readFileSync(filePath).toString('utf8');
96
- } else {
97
- EC.logRed('not found file', Util.relativePath(filePath));
154
+ }
155
+
156
+ // sourcemap data
157
+ const sourcemapData = sourceMapCache[entry.url];
158
+ if (sourcemapData) {
159
+ if (sourcemapData.data) {
160
+ entry.sourceMap = resolveSourceMap(sourcemapData.data, entry.url);
161
+ }
162
+ // sourcemapData.lineLengths just for fake source file (can not parse to AST)
163
+ if (!entry.source && sourcemapData.lineLengths) {
164
+ // get runtime code with ts-node
165
+ let source = '';
166
+ sourcemapData.lineLengths.forEach((length) => {
167
+ source += `${''.padEnd(length, '*')}\n`;
168
+ });
169
+ entry.fake = true;
170
+ entry.source = source;
171
+ }
98
172
  }
99
173
  });
100
174
 
@@ -118,18 +192,23 @@ program
118
192
  .description('CLI to generate coverage reports')
119
193
  .version(version)
120
194
  .argument('<command>', 'command to execute')
121
- .option('-c, --config <path>', 'config path for options')
195
+ .option('-c, --config <path>', 'custom config path')
122
196
  .option('-o, --outputDir <dir>', 'output dir for reports')
123
197
  .option('-r, --reports <name[,name]>', 'coverage reports to use')
124
198
  .option('-n, --name <name>', 'report name for title')
125
199
  .option('-i, --inputDir <dir>', 'input dir for merging raw files')
126
200
 
201
+ .option('--entryFilter <pattern>', 'entry url filter')
202
+ .option('--sourceFilter <pattern>', 'source path filter')
203
+
127
204
  .option('--outputFile <path>', 'output file for v8 report')
128
205
  .option('--inline', 'inline html for v8 report')
129
206
  .option('--assetsPath <path>', 'assets path if not inline')
130
207
 
131
208
  .option('--lcov', 'generate lcov.info file')
132
209
 
210
+ .option('--logging <logging>', 'off, error, info, debug')
211
+
133
212
  .action((command, cliOptions) => {
134
213
  executeCommand(command, cliOptions);
135
214
  });
@@ -306,7 +306,7 @@ const addCssRules = (list, coverageList, coveredRanges, empty) => {
306
306
  const getCssAstInfo = (item, coverageList) => {
307
307
 
308
308
  const {
309
- source, sourcePath, ranges, empty
309
+ source, ranges, empty
310
310
  } = item;
311
311
 
312
312
  // sort covered ranges
@@ -320,9 +320,7 @@ const getCssAstInfo = (item, coverageList) => {
320
320
  };
321
321
  });
322
322
 
323
- const time_start_parse = new Date();
324
323
  const ast = parseCss(source);
325
- Util.logTime(`parse css: ${sourcePath}`, time_start_parse);
326
324
 
327
325
  addCssRules(ast.nodes, coverageList, coveredRanges, empty);
328
326
 
@@ -1,4 +1,5 @@
1
1
  const fs = require('fs');
2
+ const path = require('path');
2
3
  const EC = require('eight-colors');
3
4
  const { fileURLToPath, pathToFileURL } = require('url');
4
5
  const Concurrency = require('../platform/concurrency.js');
@@ -66,16 +67,49 @@ const getSourceMapUrl = (content, url) => {
66
67
  }
67
68
  };
68
69
 
69
- const resolveSourceMap = (data) => {
70
- if (data) {
71
- const {
72
- sources, sourcesContent, mappings
73
- } = data;
74
- if (!sources || !sourcesContent || !mappings) {
75
- return;
70
+ const resolveSourcesContent = (sources, url) => {
71
+ const list = [];
72
+ for (const file of sources) {
73
+ const sourceUrl = Util.resolveUrl(file, url);
74
+ if (sourceUrl) {
75
+ const sourcePath = sourceUrl.toString();
76
+ if (sourcePath.startsWith('file:')) {
77
+ const content = Util.readFileSync(path.resolve(fileURLToPath(sourcePath)));
78
+ list.push(content);
79
+ continue;
80
+ }
76
81
  }
82
+ Util.logDebug(EC.red(`failed to load source content ${file}`));
83
+ }
84
+ if (list.length === sources.length) {
85
+ return list;
86
+ }
87
+ };
88
+
89
+ const resolveSourceMap = (data, url) => {
90
+ if (!data) {
91
+ return;
92
+ }
93
+ const {
94
+ sources, sourcesContent, mappings
95
+ } = data;
96
+
97
+ if (!sources || !mappings) {
98
+ return;
99
+ }
100
+
101
+ // exists sources content
102
+ if (sourcesContent && sourcesContent.length === sources.length) {
103
+ return data;
104
+ }
105
+
106
+ // load sources content by sources
107
+ const contents = resolveSourcesContent(sources, url);
108
+ if (contents) {
109
+ data.sourcesContent = contents;
77
110
  return data;
78
111
  }
112
+
79
113
  };
80
114
 
81
115
  const saveSourceFile = async (filePath, data) => {
@@ -92,7 +126,7 @@ const saveSourceFile = async (filePath, data) => {
92
126
 
93
127
  const collectSourceMaps = async (v8list, options) => {
94
128
 
95
- let count = 0;
129
+ const smList = [];
96
130
  const concurrency = new Concurrency();
97
131
  for (const item of v8list) {
98
132
 
@@ -130,8 +164,11 @@ const collectSourceMaps = async (v8list, options) => {
130
164
  // from inline sync
131
165
  const smc = convertSourceMap.fromSource(source);
132
166
  if (smc) {
133
- sourceData.sourceMap = resolveSourceMap(smc.sourcemap);
134
- count += 1;
167
+ sourceData.sourceMap = resolveSourceMap(smc.sourcemap, url);
168
+ smList.push({
169
+ url,
170
+ inline: true
171
+ });
135
172
  await saveSourceFile(sourcePath, sourceData);
136
173
  continue;
137
174
  }
@@ -154,17 +191,24 @@ const collectSourceMaps = async (v8list, options) => {
154
191
 
155
192
  // from url concurrency
156
193
  await concurrency.start(async (item) => {
157
- const sourceData = item.sourceData;
158
- const data = await loadSourceMap(item.sourceMapUrl);
194
+ const { sourceMapUrl, sourceData } = item;
195
+ const { url } = sourceData;
196
+ const data = await loadSourceMap(sourceMapUrl);
159
197
  if (data) {
160
- sourceData.sourceMap = resolveSourceMap(data);
161
- count += 1;
198
+ sourceData.sourceMap = resolveSourceMap(data, url);
199
+ smList.push({
200
+ url,
201
+ sourceMapUrl
202
+ });
162
203
  }
163
204
  await saveSourceFile(item.sourcePath, sourceData);
164
205
  });
165
206
 
166
- return count;
207
+ return smList;
167
208
 
168
209
  };
169
210
 
170
- module.exports = collectSourceMaps;
211
+ module.exports = {
212
+ collectSourceMaps,
213
+ resolveSourceMap
214
+ };
@@ -15,7 +15,7 @@ const {
15
15
  sortRanges, dedupeCountRanges, mergeRangesWith
16
16
  } = require('../utils/dedupe.js');
17
17
  const { getSourceType, initSourceMapSourcePath } = require('../utils/source-path.js');
18
- const { decode } = require('../packages/monocart-coverage-vendor.js');
18
+ const { decode, minimatch } = require('../packages/monocart-coverage-vendor.js');
19
19
 
20
20
  const InfoBranch = require('./info-branch.js');
21
21
  const InfoFunction = require('./info-function.js');
@@ -838,13 +838,25 @@ const getOriginalDecodedMappings = (originalDecodedMap, sourceIndex, locator) =>
838
838
 
839
839
  // ========================================================================================================
840
840
 
841
+ const getSourceFilter = (options) => {
842
+ const sourceFilter = options.sourceFilter;
843
+ if (typeof sourceFilter === 'function') {
844
+ return sourceFilter;
845
+ }
846
+
847
+ if (sourceFilter && typeof sourceFilter === 'string') {
848
+ return (sourcePath) => {
849
+ return minimatch(sourcePath, sourceFilter);
850
+ };
851
+ }
852
+
853
+ return () => true;
854
+ };
855
+
841
856
  const initOriginalList = (state, originalDecodedMap, options) => {
842
857
 
843
858
  // source filter
844
- let sourceFilter = options.sourceFilter;
845
- if (typeof sourceFilter !== 'function') {
846
- sourceFilter = () => true;
847
- }
859
+ const sourceFilter = getSourceFilter(options);
848
860
 
849
861
  // create original content mappings
850
862
  const originalStateMap = new Map();
@@ -855,8 +867,10 @@ const initOriginalList = (state, originalDecodedMap, options) => {
855
867
 
856
868
  // filter
857
869
  if (!sourceFilter(sourcePath)) {
870
+ // console.log('-', sourcePath);
858
871
  return;
859
872
  }
873
+ // console.log(sourcePath);
860
874
 
861
875
  // console.log(`add source: ${k}`);
862
876
  const sourceContent = sourcesContent[sourceIndex];
@@ -1300,6 +1314,7 @@ const convertV8List = (v8list, options) => {
1300
1314
  }
1301
1315
 
1302
1316
  // ============================
1317
+ const time_start_ast = Date.now();
1303
1318
  // move functions and ranges to coverageList
1304
1319
  let coverageList = [];
1305
1320
  let astInfo;
@@ -1318,6 +1333,7 @@ const convertV8List = (v8list, options) => {
1318
1333
  delete item.ranges;
1319
1334
  }
1320
1335
  }
1336
+ Util.logTime(`- parsed ast: ${sourcePath}`, time_start_ast);
1321
1337
 
1322
1338
  // console.log(sourcePath, astInfo.statements.length);
1323
1339
  // ============================
@@ -1345,14 +1361,19 @@ const convertV8List = (v8list, options) => {
1345
1361
  v8Data: item
1346
1362
  };
1347
1363
 
1364
+ const time_start_unpack = Date.now();
1348
1365
  unpackDistFile(item, state, options);
1366
+ Util.logTime(`- unpacked dist: ${sourcePath}`, time_start_unpack);
1349
1367
 
1350
1368
  stateList.push(state);
1351
1369
 
1352
1370
  }
1353
1371
 
1354
- return generateV8DataList(stateList, options);
1372
+ const time_start_convert = Date.now();
1373
+ const dataList = generateV8DataList(stateList, options);
1374
+ Util.logTime('- generated v8 data list', time_start_convert);
1355
1375
 
1376
+ return dataList;
1356
1377
  };
1357
1378
 
1358
1379
  module.exports = {
package/lib/generate.js CHANGED
@@ -247,7 +247,7 @@ const getCoverageResults = async (dataList, sourceCache, options) => {
247
247
  const {
248
248
  v8DataList, coverageData, fileSources
249
249
  } = results;
250
- Util.logTime(`converted v8 data: ${v8DataList.length}`, time_start);
250
+ Util.logTime(`converted v8 list: ${v8DataList.length}`, time_start);
251
251
 
252
252
  return generateV8ListReports(v8DataList, coverageData, fileSources, options);
253
253
  }
package/lib/index.d.ts CHANGED
@@ -198,9 +198,14 @@ declare namespace MCR {
198
198
  /** {string|string[]} input raw dir(s) */
199
199
  inputDir?: string | string[];
200
200
 
201
- /** {string} v8 or html for istanbul by default
202
- * {array} multiple reports with options
203
- * v8 report or istanbul supported reports */
201
+ /**
202
+ *
203
+ * {string} 'v8', 'v8,console-details'
204
+ *
205
+ * {array} ['v8'], ['v8', ['console-details', { skipPercent: 80 }]]
206
+ *
207
+ * By default, `v8` for V8 data, `html` for Istanbul data
208
+ */
204
209
  reports?: string | ReportDescription[];
205
210
 
206
211
  /** {string} Report name. Defaults to "Coverage Report". */
@@ -213,11 +218,21 @@ declare namespace MCR {
213
218
  /** (V8 only) {string} Assets path if not inline. Defaults to "./assets" */
214
219
  assetsPath?: string;
215
220
 
216
- /** (V8 only) {function} A filter function to execute for each element in the V8 list. */
217
- entryFilter?: (entry: V8CoverageEntry) => boolean;
218
-
219
- /** (V8 only) {function} A filter function to execute for each element in the sources which unpacked from the source map. */
220
- sourceFilter?: (sourcePath: string) => boolean;
221
+ /** (V8 only)
222
+ *
223
+ * {string} `minimatch` pattern for entry url; multiple patterns: "{pattern1,pattern2}";
224
+ *
225
+ * {function} A filter function for each entry file in the V8 list.
226
+ * */
227
+ entryFilter?: string | ((entry: V8CoverageEntry) => boolean);
228
+
229
+ /** (V8 only)
230
+ *
231
+ * {string} `minimatch` pattern for source path; multiple patterns: "{pattern1,pattern2}";
232
+ *
233
+ * {function} A filter function for each source path when the source is unpacked from the source map.
234
+ * */
235
+ sourceFilter?: string | ((sourcePath: string) => boolean);
221
236
 
222
237
  /** (V8 only) {boolean} Enable/Disable ignoring uncovered codes with the special comments: v8 ignore next/next N/start/stop */
223
238
  v8Ignore?: boolean;