1 | 'use strict';
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | var _babelTemplate = require('babel-template');
|
8 |
|
9 | var _babelTemplate2 = _interopRequireDefault(_babelTemplate);
|
10 |
|
11 | var _babelHelperFunctionName = require('babel-helper-function-name');
|
12 |
|
13 | var _babelHelperFunctionName2 = _interopRequireDefault(_babelHelperFunctionName);
|
14 |
|
15 | var _fs = require('fs');
|
16 |
|
17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
18 |
|
19 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
|
20 |
|
21 | var coverageTemplate = (0, _babelTemplate2.default)('\n var GLOBAL = (new Function(\'return this\'))()\n var COVERAGE = GLOBAL[\'__coverage__\'] || (GLOBAL[\'__coverage__\'] = { })\n var FILE_COVERAGE = COVERAGE[PATH] = GLOBAL[\'JSON\'].parse(INITIAL)\n');
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | function getRealpath(n) {
|
28 | try {
|
29 | return (0, _fs.realpathSync)(n) || n;
|
30 | } catch (e) {
|
31 | return n;
|
32 | }
|
33 | }
|
34 |
|
35 | module.exports = function (_ref) {
|
36 | var t = _ref.types;
|
37 |
|
38 |
|
39 |
|
40 |
|
41 | function getData(context) {
|
42 | var path = getRealpath(context.file.opts.filename);
|
43 |
|
44 |
|
45 |
|
46 | return context.file.__coverage__data || (context.file.__coverage__data = {
|
47 |
|
48 |
|
49 |
|
50 | base: {
|
51 | path: path,
|
52 | s: {},
|
53 | b: {},
|
54 | f: {},
|
55 | statementMap: {},
|
56 | fnMap: {},
|
57 | branchMap: {}
|
58 | },
|
59 |
|
60 |
|
61 | nextId: {
|
62 | s: 1,
|
63 | b: 1,
|
64 | f: 1
|
65 | }
|
66 | });
|
67 | }
|
68 |
|
69 |
|
70 |
|
71 |
|
72 | function locToObject(loc) {
|
73 | return {
|
74 | start: {
|
75 | line: loc.start.line,
|
76 | column: loc.start.column
|
77 | },
|
78 | end: {
|
79 | line: loc.end.line,
|
80 | column: loc.end.column
|
81 | }
|
82 | };
|
83 | }
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 | function increase(context, type, id, index) {
|
90 | var wrap = index != null
|
91 |
|
92 | ? function (x) {
|
93 | return t.memberExpression(x, t.numericLiteral(index), true);
|
94 | } : function (x) {
|
95 | return x;
|
96 | };
|
97 | return t.unaryExpression('++', wrap(t.memberExpression(t.memberExpression(getData(context).id, t.identifier(type)), t.stringLiteral(id), true)));
|
98 | }
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 | function instrument(path, increment) {
|
107 | if (path.isStatement()) {
|
108 | path.insertBefore(t.expressionStatement(increment));
|
109 | } else if (path.isExpression()) {
|
110 | path.replaceWith(t.sequenceExpression([increment, path.node]));
|
111 | } else {
|
112 | throw new Error('wtf? I can’t cover a ' + path.node.type + '!!!!??');
|
113 | }
|
114 | }
|
115 |
|
116 |
|
117 |
|
118 |
|
119 | function instrumentStatement(context, path) {
|
120 | var node = path.node;
|
121 |
|
122 |
|
123 | if (!node.loc) return;
|
124 |
|
125 |
|
126 |
|
127 | if (node.__coverage__instrumented) return;
|
128 | node.__coverage__instrumented = true;
|
129 |
|
130 | var data = getData(context);
|
131 | var id = String(data.nextId.s++);
|
132 | data.base.s[id] = 0;
|
133 | data.base.statementMap[id] = locToObject(node.loc);
|
134 | instrument(path, increase(context, 's', id));
|
135 | }
|
136 |
|
137 |
|
138 |
|
139 |
|
140 | function nextBranchId(context, line, type, locations) {
|
141 | var data = getData(context);
|
142 | var id = String(data.nextId.b++);
|
143 | data.base.b[id] = locations.map(function () {
|
144 | return 0;
|
145 | });
|
146 | data.base.branchMap[id] = { line: line, type: type, locations: locations.map(locToObject) };
|
147 | return id;
|
148 | }
|
149 |
|
150 |
|
151 |
|
152 |
|
153 | function coverStatement(path) {
|
154 | instrumentStatement(this, path);
|
155 | }
|
156 |
|
157 |
|
158 |
|
159 |
|
160 | function coverVariableDeclarator(path) {
|
161 | if (!path.node.init) return;
|
162 | instrumentStatement(this, path.get('init'));
|
163 | }
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | function coverIfStatement(path) {
|
169 | if (!path.node.loc) return;
|
170 | instrumentStatement(this, path);
|
171 | if (!path.get('consequent').node) path.set('consequent', t.emptyStatement());
|
172 | if (!path.get('alternate').node) path.set('alternate', t.emptyStatement());
|
173 | var node = path.node;
|
174 | var loc1 = node.consequent.loc || node.loc;
|
175 | var loc2 = node.alternate.loc || loc1;
|
176 | var id = nextBranchId(this, node.loc.start.line, 'if', [loc1, loc2]);
|
177 | instrument(path.get('consequent'), increase(this, 'b', id, 0));
|
178 | instrument(path.get('alternate'), increase(this, 'b', id, 1));
|
179 | }
|
180 |
|
181 |
|
182 |
|
183 |
|
184 | function coverSwitchStatement(path) {
|
185 | if (!path.node.loc) return;
|
186 | instrumentStatement(this, path);
|
187 | var validCases = path.get('cases').filter(function (p) {
|
188 | return p.node.loc;
|
189 | });
|
190 | var id = nextBranchId(this, path.node.loc.start.line, 'switch', validCases.map(function (p) {
|
191 | return p.node.loc;
|
192 | }));
|
193 | var index = 0;
|
194 | var _iteratorNormalCompletion = true;
|
195 | var _didIteratorError = false;
|
196 | var _iteratorError = undefined;
|
197 |
|
198 | try {
|
199 | for (var _iterator = validCases[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
200 | var p = _step.value;
|
201 |
|
202 | if (p.node.test) {
|
203 | instrumentStatement(this, p.get('test'));
|
204 | }
|
205 | p.node.consequent.unshift(increase(this, 'b', id, index++));
|
206 | }
|
207 | } catch (err) {
|
208 | _didIteratorError = true;
|
209 | _iteratorError = err;
|
210 | } finally {
|
211 | try {
|
212 | if (!_iteratorNormalCompletion && _iterator.return) {
|
213 | _iterator.return();
|
214 | }
|
215 | } finally {
|
216 | if (_didIteratorError) {
|
217 | throw _iteratorError;
|
218 | }
|
219 | }
|
220 | }
|
221 | }
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 | function coverForStatement(path) {
|
228 | instrumentStatement(this, path);
|
229 | if (path.get('update').node) {
|
230 | instrumentStatement(this, path.get('update'));
|
231 | }
|
232 | }
|
233 |
|
234 |
|
235 |
|
236 |
|
237 | function coverFunction(path) {
|
238 | if (!path.node.loc) return;
|
239 | var node = path.node;
|
240 | var data = getData(this);
|
241 | var id = String(data.nextId.f++);
|
242 | var nameOf = function nameOf(namedNode) {
|
243 | return namedNode && namedNode.id && namedNode.id.name || null;
|
244 | };
|
245 | data.base.f[id] = 0;
|
246 | data.base.fnMap[id] = {
|
247 | name: nameOf((0, _babelHelperFunctionName2.default)(path)),
|
248 | line: node.loc.start.line,
|
249 | loc: locToObject(node.loc)
|
250 | };
|
251 | var increment = increase(this, 'f', id);
|
252 | var body = path.get('body');
|
253 | if (body.isBlockStatement()) {
|
254 | body.node.body.unshift(t.expressionStatement(increment));
|
255 | } else if (body.isExpression()) {
|
256 | body.replaceWith(t.sequenceExpression([increment, body.node]));
|
257 | } else {
|
258 | throw new Error('wtf?? Can’t cover function with ' + body.node.type);
|
259 | }
|
260 | }
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 | function coverConditionalExpression(path) {
|
267 | if (!path.node.loc) return;
|
268 | var node = path.node;
|
269 | var loc1 = node.consequent.loc || node.loc;
|
270 | var loc2 = node.alternate.loc || loc1;
|
271 | var id = nextBranchId(this, node.loc.start.line, 'cond-expr', [loc1, loc2]);
|
272 | instrumentStatement(this, path.get('consequent'));
|
273 | instrumentStatement(this, path.get('alternate'));
|
274 | instrument(path.get('consequent'), increase(this, 'b', id, 0));
|
275 | instrument(path.get('alternate'), increase(this, 'b', id, 1));
|
276 | }
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 | function coverLogicalExpression(path) {
|
283 | if (!path.node.loc) return;
|
284 | var node = path.node;
|
285 | var loc1 = node.left.loc || node.loc;
|
286 | var loc2 = node.right.loc || loc1;
|
287 | var id = nextBranchId(this, node.loc.start.line, 'binary-expr', [loc1, loc2]);
|
288 |
|
289 | instrumentStatement(this, path.get('right'));
|
290 | instrument(path.get('left'), increase(this, 'b', id, 0));
|
291 | instrument(path.get('right'), increase(this, 'b', id, 1));
|
292 | }
|
293 |
|
294 | return {
|
295 | visitor: {
|
296 |
|
297 |
|
298 |
|
299 | ExpressionStatement: coverStatement,
|
300 | BreakStatement: coverStatement,
|
301 | ContinueStatement: coverStatement,
|
302 | DebuggerStatement: coverStatement,
|
303 | ReturnStatement: coverStatement,
|
304 | ThrowStatement: coverStatement,
|
305 | TryStatement: coverStatement,
|
306 | VariableDeclarator: coverVariableDeclarator,
|
307 | IfStatement: coverIfStatement,
|
308 | ForStatement: coverForStatement,
|
309 | ForInStatement: coverStatement,
|
310 | ForOfStatement: coverStatement,
|
311 | WhileStatement: coverStatement,
|
312 | DoWhileStatement: coverStatement,
|
313 | SwitchStatement: coverSwitchStatement,
|
314 | ArrowFunctionExpression: coverFunction,
|
315 | FunctionExpression: coverFunction,
|
316 | FunctionDeclaration: coverFunction,
|
317 | LabeledStatement: coverStatement,
|
318 | ConditionalExpression: coverConditionalExpression,
|
319 | LogicalExpression: coverLogicalExpression,
|
320 |
|
321 | Program: {
|
322 | enter: function enter(path) {
|
323 |
|
324 | getData(this).id = path.scope.generateUidIdentifier('__coverage__file');
|
325 | },
|
326 | exit: function exit(path) {
|
327 | var _path$node$body;
|
328 |
|
329 |
|
330 | var realPath = getRealpath(this.file.opts.filename);
|
331 | (_path$node$body = path.node.body).unshift.apply(_path$node$body, _toConsumableArray(coverageTemplate({
|
332 | GLOBAL: path.scope.generateUidIdentifier('__coverage__global'),
|
333 | COVERAGE: path.scope.generateUidIdentifier('__coverage__object'),
|
334 | FILE_COVERAGE: getData(this).id,
|
335 | PATH: t.stringLiteral(realPath),
|
336 | INITIAL: t.stringLiteral(JSON.stringify(getData(this).base))
|
337 | })));
|
338 | }
|
339 | }
|
340 | }
|
341 | };
|
342 | }; |
\ | No newline at end of file |