escover 1.0.0

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 ADDED
@@ -0,0 +1,38 @@
1
+ # 🎩`ESCover` [![License][LicenseIMGURL]][LicenseURL] [![NPM version][NPMIMGURL]][NPMURL] [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Coverage Status][CoverageIMGURL]][CoverageURL]
2
+
3
+ [NPMIMGURL]: https://img.shields.io/npm/v/escover.svg?style=flat
4
+ [BuildStatusURL]: https://github.com/coderaiser/escover/actions?query=workflow%3A%22Node+CI%22 "Build Status"
5
+ [BuildStatusIMGURL]: https://github.com/coderaiser/escover/workflows/Node%20CI/badge.svg
6
+ [LicenseIMGURL]: https://img.shields.io/badge/license-MIT-317BF9.svg?style=flat
7
+ [NPMURL]: https://npmjs.org/package/escover "npm"
8
+ [LicenseURL]: https://tldrlegal.com/license/mit-license "MIT License"
9
+ [CoverageURL]: https://coveralls.io/github/coderaiser/escover?branch=master
10
+ [CoverageIMGURL]: https://coveralls.io/repos/coderaiser/escover/badge.svg?branch=master&service=github
11
+
12
+ Explosive coverage tool
13
+
14
+ ## Install
15
+
16
+ ```
17
+ npm i escover -g
18
+ ```
19
+
20
+ Then run using:
21
+
22
+ ```sh
23
+ NODE_OPTIONS="'--loader escover'" escover npm test
24
+ ```
25
+
26
+ ## How it looks like?
27
+
28
+ When everything is covered:
29
+
30
+ ![image](https://user-images.githubusercontent.com/1573141/147943954-ef708577-2856-4de0-9397-dead487b8c08.png)
31
+
32
+ When some lines missing coverage:
33
+
34
+ ![image](https://user-images.githubusercontent.com/1573141/147944130-9b901646-05ff-4a76-86c9-30631b0a0dd4.png)
35
+
36
+ ## License
37
+
38
+ MIT
package/bin/c4.js ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+
3
+ import chalk from 'chalk';
4
+
5
+ import {readFileSync} from 'fs';
6
+
7
+ const {parse} = JSON;
8
+ const coverageFile = parse(readFileSync('./coverage.json', 'utf8'));
9
+
10
+ const files = [];
11
+ const coverage = {
12
+ files,
13
+ coveredCount: 0,
14
+ uncoveredCount: 0,
15
+ };
16
+
17
+ console.log('# TAP version 13');
18
+ console.log('');
19
+
20
+ for (const {name, lines} of coverageFile) {
21
+ const uncoveredLines = [];
22
+
23
+ for (const [line, covered] of Object.entries(lines)) {
24
+ if (covered)
25
+ continue;
26
+
27
+ uncoveredLines.push(line);
28
+ }
29
+
30
+ const file = {
31
+ name,
32
+ covered: !uncoveredLines.length,
33
+ uncoveredLines,
34
+ };
35
+
36
+ if (file.covered)
37
+ ++coverage.coveredCount;
38
+
39
+ if (!file.covered)
40
+ ++coverage.uncoveredCount;
41
+
42
+ files.push(file);
43
+ }
44
+
45
+ for (const {name, covered, uncoveredLines} of files) {
46
+ if (!covered) {
47
+ console.log(`# ${name}`);
48
+ console.log('❌ should be covered');
49
+ console.log('---');
50
+ console.log(`lines: ${chalk.red(uncoveredLines.join(','))}`);
51
+ }
52
+ }
53
+
54
+ if (coverage.uncoveredCount)
55
+ console.log('');
56
+
57
+ console.log(`1..${files.length}`);
58
+ console.log(`# files: ${files.length}`);
59
+ console.log(`# covered: ${coverage.coveredCount}`);
60
+
61
+ if (!coverage.uncoveredCount) {
62
+ console.log('');
63
+ console.log('# ☘️ ok');
64
+ }
65
+
66
+ if (coverage.uncoveredCount) {
67
+ console.log(`# 🧨 fail: ${coverage.uncoveredCount}`);
68
+ }
69
+
package/c4.json ADDED
@@ -0,0 +1,15 @@
1
+ [{
2
+ "name": "changelog.js",
3
+ "lines": {
4
+ "1": false,
5
+ "7": false,
6
+ "9": false
7
+ }
8
+ }, {
9
+ "name": "simple.js",
10
+ "lines": {
11
+ "1": true,
12
+ "7": true,
13
+ "9": true
14
+ }
15
+ }]
package/coverage.json ADDED
@@ -0,0 +1 @@
1
+ []
@@ -0,0 +1,4 @@
1
+ export const sum = (a, b) => a + b;
2
+
3
+ export const mul = (a, b) => a * b;
4
+
package/lib/c4.js ADDED
@@ -0,0 +1,36 @@
1
+ import montag from 'montag';
2
+ import process from 'process';
3
+ import {instrument} from './instrument/index.js';
4
+ import {exclude} from './exclude.js';
5
+ import {save} from './save.js';
6
+ import {createFileEntry} from './report.js';
7
+
8
+ global.__createC4 = createFileEntry;
9
+
10
+ export async function load(url, context, defaultLoad) {
11
+ const {format, source: rawSource} = await defaultLoad(url, context, defaultLoad);
12
+
13
+ if (/commonjs|builtin/.test(format))
14
+ return {
15
+ format,
16
+ };
17
+
18
+ if (exclude(url, ['.spec.js', 'node_modules']))
19
+ return {
20
+ format,
21
+ source: rawSource,
22
+ };
23
+
24
+ const source = montag`
25
+ const __c4 = global.__createC4('${url}');
26
+ ${instrument(url, rawSource)}
27
+ `;
28
+
29
+ return {
30
+ format,
31
+ source: `${source}\n console.log('🧨', '${url}')`,
32
+ };
33
+ }
34
+
35
+ process.once('exit', save);
36
+
package/lib/exclude.js ADDED
@@ -0,0 +1,8 @@
1
+ export const exclude = (url, names) => {
2
+ for (const name of names) {
3
+ if (url.includes(name))
4
+ return true;
5
+ }
6
+
7
+ return false;
8
+ };
@@ -0,0 +1,9 @@
1
+ const {__c4} = global;
2
+ __c4.mark(1, 0), cli({
3
+ stdout,
4
+ stderr,
5
+ exit,
6
+ cwd: (__c4.mark(5, 9), process.cwd()),
7
+ argv: (__c4.mark(6, 10), process.argv.slice(2))
8
+ });
9
+
@@ -0,0 +1,8 @@
1
+ cli({
2
+ stdout,
3
+ stderr,
4
+ exit,
5
+ cwd: process.cwd(),
6
+ argv: process.argv.slice(2)
7
+ })
8
+
@@ -0,0 +1,21 @@
1
+ import putout, {} from 'putout';
2
+
3
+ import * as markLine from './plugin-mark-line/index.js';
4
+
5
+ export const instrument = (url, source) => {
6
+ const __c4 = global.__createC4(url);
7
+ const options = {
8
+ rules: {
9
+ 'mark-line': ['on', {
10
+ __c4,
11
+ }],
12
+ },
13
+ plugins: [
14
+ ['mark-line', markLine],
15
+ ],
16
+ };
17
+
18
+ const {code} = putout(source, options);
19
+ return code;
20
+ };
21
+
@@ -0,0 +1,14 @@
1
+ import {types} from 'putout';
2
+
3
+ const {SequenceExpression} = types;
4
+
5
+ export const addMarkToArrowFunction = (path, lineNode) => {
6
+ const {node} = path;
7
+ const {expression} = lineNode;
8
+
9
+ node.body = SequenceExpression([
10
+ expression,
11
+ node.body,
12
+ ]);
13
+ };
14
+
@@ -0,0 +1,6 @@
1
+ export const nothing = (a, b) => {
2
+ return __c4.mark(2, 4);
3
+ };
4
+ export const sum = (a, b) => {
5
+ return __c4.mark(5, 4), a + b;
6
+ };
@@ -0,0 +1,6 @@
1
+ export const nothing = (a, b) => {
2
+ return;
3
+ };
4
+ export const sum = (a, b) => {
5
+ return a + b;
6
+ };
@@ -0,0 +1,3 @@
1
+ if ((__c4.mark(1, 4), a) || (__c4.mark(1, 9), b)) {
2
+ __c4.mark(1, 12);
3
+ }
@@ -0,0 +1,2 @@
1
+ if (a || b) {
2
+ }
@@ -0,0 +1,30 @@
1
+ const a = () => {
2
+ a = 5;
3
+ __c4.mark(3, 4), console.log(5);
4
+ };
5
+
6
+
7
+ function x() {
8
+
9
+ if (a > 2) {
10
+ __c4.mark(9, 14), a();
11
+ } else {
12
+ __c4.mark(10, 9), b();
13
+ }
14
+ }
15
+
16
+ for (const x of y) {
17
+ __c4.mark(13, 19);
18
+ }
19
+
20
+ const ax = ((__c4.mark(16, 12), a()), (__c4.mark(16, 17), b()));
21
+ const bx = (c, (__c4.mark(17, 15), d()));
22
+
23
+ if ((__c4.mark(19, 4), a) || (__c4.mark(19, 9), b)) {
24
+ __c4.mark(19, 12);
25
+ }
26
+
27
+
28
+ function x1(a, b = (__c4.mark(23, 15), b)) {
29
+ __c4.mark(23, 22);
30
+ }
@@ -0,0 +1,24 @@
1
+ const a = () => {
2
+ a = 5;
3
+ console.log(5);
4
+ };
5
+
6
+
7
+ function x() {
8
+
9
+ if (a > 2)a()
10
+ else b();
11
+ }
12
+
13
+ for (const x of y) {
14
+ }
15
+
16
+ const ax = (a(), b());
17
+ const bx = (c, d());
18
+
19
+ if (a || b) {
20
+ }
21
+
22
+
23
+ function x1(a, b = 5) {
24
+ }
@@ -0,0 +1 @@
1
+ const files = (__c4.mark(1, 14), new Map());
@@ -0,0 +1 @@
1
+ const files = new Map();
@@ -0,0 +1,8 @@
1
+ __c4.mark(1, 0), cli({
2
+ stdout,
3
+ stderr,
4
+ exit,
5
+ cwd: (__c4.mark(5, 9), process.cwd()),
6
+ argv: (__c4.mark(6, 10), process.argv.slice(2))
7
+ });
8
+
@@ -0,0 +1,8 @@
1
+ cli({
2
+ stdout,
3
+ stderr,
4
+ exit,
5
+ cwd: process.cwd(),
6
+ argv: process.argv.slice(2)
7
+ })
8
+
@@ -0,0 +1,169 @@
1
+ import {
2
+ template,
3
+ types,
4
+ operator,
5
+ } from 'putout';
6
+ import {addMarkToReturn} from './return.js';
7
+ import {addMarkToArrowFunction} from './arrow.js';
8
+
9
+ const {
10
+ NumericLiteral,
11
+ SequenceExpression,
12
+ BlockStatement,
13
+ } = types;
14
+
15
+ const {
16
+ replaceWithMultiple,
17
+ replaceWith,
18
+ compareAny,
19
+ } = operator;
20
+
21
+ const LINE = '__c4.mark(__l, __c)';
22
+ const buildLineNode = template(LINE, {
23
+ placeholderPattern: /^__[a-z]$/,
24
+ });
25
+
26
+ function getLineNode(__c4, {line, column}) {
27
+ __c4.init(line, column);
28
+
29
+ return buildLineNode({
30
+ __l: NumericLiteral(line),
31
+ __c: NumericLiteral(column),
32
+ });
33
+ }
34
+
35
+ export const report = () => 'Mark line';
36
+
37
+ export const fix = (path, {options}) => {
38
+ const {
39
+ parentPath,
40
+ node,
41
+ } = path;
42
+
43
+ const {start} = path.node.loc || parentPath.node.loc || {};
44
+
45
+ if (!start)
46
+ return;
47
+
48
+ const {
49
+ __c4 = {
50
+ mark: () => {},
51
+ init: () => {},
52
+ },
53
+ } = options;
54
+
55
+ const lineNode = getLineNode(__c4, start);
56
+
57
+ if (path.isBlockStatement()) {
58
+ path.node.body.unshift(lineNode);
59
+ return;
60
+ }
61
+
62
+ if (path.isCallExpression()) {
63
+ const {node} = path;
64
+ replaceWith(path, SequenceExpression([
65
+ lineNode.expression,
66
+ node,
67
+ ]));
68
+ return;
69
+ }
70
+
71
+ if (path.isLogicalExpression()) {
72
+ replaceWith(path.get('left'), SequenceExpression([
73
+ lineNode.expression,
74
+ path.node.left,
75
+ ]));
76
+ replaceWith(path.get('right'), SequenceExpression([
77
+ getLineNode(__c4, path.node.right.loc.start).expression,
78
+ path.node.right,
79
+ ]));
80
+ return;
81
+ }
82
+
83
+ if (path.isAssignmentPattern()) {
84
+ replaceWithMultiple(path.get('right'), [
85
+ lineNode,
86
+ node.left,
87
+ ]);
88
+ return;
89
+ }
90
+
91
+ if (path.isNewExpression()) {
92
+ replaceWith(path, SequenceExpression([
93
+ lineNode.expression,
94
+ node,
95
+ ]));
96
+ return;
97
+ }
98
+
99
+ if (path.isReturnStatement())
100
+ return addMarkToReturn(path, lineNode);
101
+
102
+ if (path.isArrowFunctionExpression())
103
+ return addMarkToArrowFunction(path, lineNode);
104
+
105
+ replaceWith(path, BlockStatement([
106
+ node,
107
+ ]));
108
+ };
109
+
110
+ const EXCLUDE = [
111
+ LINE,
112
+ `(${LINE}, __z)`,
113
+ `return (${LINE}, __z)`,
114
+ `return ${LINE}`,
115
+ ];
116
+
117
+ export const exclude = () => EXCLUDE;
118
+
119
+ export const include = () => [
120
+ 'AssignmentPattern',
121
+ 'CallExpression',
122
+ 'NewExpression',
123
+ 'ReturnStatement',
124
+ ];
125
+
126
+ export const traverse = ({push}) => ({
127
+ ArrowFunctionExpression(path) {
128
+ if (path.get('body').isBlockStatement())
129
+ return;
130
+
131
+ push(path);
132
+ },
133
+ LogicalExpression(path) {
134
+ if (compareAny(path.get('left'), EXCLUDE))
135
+ return;
136
+
137
+ push(path);
138
+ },
139
+ BlockStatement(path) {
140
+ if (path.node.body.length)
141
+ return;
142
+
143
+ push(path);
144
+ },
145
+ SequenceExpression(path) {
146
+ const expressions = path.get('expressions');
147
+
148
+ for (const expPath of expressions) {
149
+ if (!expPath.isCallExpression())
150
+ continue;
151
+
152
+ push(expPath);
153
+ }
154
+ },
155
+ IfStatement(path) {
156
+ const consequentPath = path.get('consequent');
157
+ const alternatePath = path.get('alternate');
158
+
159
+ if (!consequentPath.isBlockStatement())
160
+ push(consequentPath);
161
+
162
+ if (!alternatePath.node)
163
+ return;
164
+
165
+ if (!alternatePath.isBlockStatement())
166
+ push(alternatePath);
167
+ },
168
+ });
169
+
@@ -0,0 +1,17 @@
1
+ import {types} from 'putout';
2
+ const {SequenceExpression} = types;
3
+
4
+ export const addMarkToReturn = (path, lineNode) => {
5
+ const {node} = path;
6
+ const {expression} = lineNode;
7
+
8
+ if (!node.argument) {
9
+ node.argument = expression;
10
+ return;
11
+ }
12
+
13
+ node.argument = SequenceExpression([
14
+ expression,
15
+ node.argument,
16
+ ]);
17
+ };
package/lib/report.js ADDED
@@ -0,0 +1,17 @@
1
+ const files = new Map();
2
+
3
+ export const createFileEntry = (url) => {
4
+ const lines = files.get(url) || new Map();
5
+ files.set(url, lines);
6
+
7
+ return {
8
+ mark: (line, column) => {
9
+ lines.set(`${line}:${column}`, true);
10
+ },
11
+ init: (line, column) => {
12
+ lines.set(`${line}:${column}`, false);
13
+ },
14
+ };
15
+ };
16
+
17
+ export const getFiles = () => files;
package/lib/save.js ADDED
@@ -0,0 +1,27 @@
1
+ import {writeFileSync} from 'fs';
2
+ import once from 'once';
3
+ import {getFiles} from './report.js';
4
+
5
+ const {stringify} = JSON;
6
+
7
+ export const save = once(() => {
8
+ const files = getFiles();
9
+ const report = [];
10
+ for (const [name, places] of files.entries()) {
11
+ const lines = {};
12
+ const current = {
13
+ name,
14
+ lines,
15
+ };
16
+ for (const [place, covered] of places.entries()) {
17
+ const [line] = place.split(':');
18
+
19
+ lines[line] = covered;
20
+ }
21
+
22
+ report.push(current);
23
+ }
24
+
25
+ writeFileSync('./coverage.json', stringify(report, null, 4));
26
+ });
27
+
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "escover",
3
+ "version": "1.0.0",
4
+ "author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
5
+ "description": "Coverage for EcmaScript Modules",
6
+ "main": "lib/escover.js",
7
+ "type": "module",
8
+ "bin": {
9
+ "escover": "bin/escover.js"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git://github.com/coderaiser/escover.git"
14
+ },
15
+ "keywords": [
16
+ "coverage",
17
+ "putout",
18
+ "loader"
19
+ ],
20
+ "scripts": {
21
+ "test": "madrun test",
22
+ "coverage": "madrun coverage",
23
+ "lint": "madrun lint",
24
+ "fresh:lint": "madrun fresh:lint",
25
+ "lint:fresh": "madrun lint:fresh",
26
+ "fix:lint": "madrun fix:lint",
27
+ "report": "madrun report",
28
+ "watcher": "madrun watcher",
29
+ "watch:test": "madrun watch:test",
30
+ "watch:lint": "madrun watch:lint",
31
+ "watch:tape": "madrun watch:tape",
32
+ "watch:coverage": "madrun watch:coverage"
33
+ },
34
+ "dependencies": {
35
+ "chalk": "^5.0.0",
36
+ "montag": "^1.2.1",
37
+ "once": "^1.4.0",
38
+ "putout": "^23.5.0"
39
+ },
40
+ "engines": {
41
+ "node": ">=14"
42
+ },
43
+ "license": "MIT",
44
+ "devDependencies": {
45
+ "@putout/test": "^4.1.0",
46
+ "c8": "^7.8.0",
47
+ "eslint": "^8.3.0",
48
+ "eslint-plugin-node": "^11.1.0",
49
+ "eslint-plugin-putout": "^12.2.0",
50
+ "madrun": "^8.8.1",
51
+ "mock-import": "^2.0.0",
52
+ "supertape": "^6.0.5",
53
+ "zenload": "^1.0.0"
54
+ }
55
+ }