├── .editorconfig
├── .gitignore
├── .jshintrc
├── .travis.yml
├── LICENSE
├── README.md
├── fixtures
├── react-es5.instrumented
├── react-es5.jsx
├── react-es6-invalid.jsx
├── react-es6.instrumented
└── react-es6.jsx
├── index.js
├── package.json
└── test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .idea
3 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "esnext": true,
4 | "bitwise": true,
5 | "camelcase": true,
6 | "curly": true,
7 | "immed": true,
8 | "newcap": true,
9 | "noarg": true,
10 | "undef": true,
11 | "unused": "vars",
12 | "strict": true
13 | }
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '0.12'
4 | - '0.11'
5 | - '0.10'
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Podio
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # istanbul-react [](https://travis-ci.org/podio/istanbul-react) [](https://gemnasium.com/podio/istanbul-react)
2 | > Instrumenter for 1:1 mapping of React JSX components, can be used with [karma-coverage](https://github.com/karma-runner/karma-coverage)
3 |
4 | ## Install
5 |
6 | ```sh
7 | $ npm install --save-dev istanbul-react
8 | ```
9 |
10 | ## Usage
11 |
12 | Use with [karma-coverage](https://github.com/karma-runner/karma-coverage#instrumenter)
13 |
14 | ```js
15 | coverageReporter: {
16 | instrumenters: { 'istanbul-react' : require('istanbul-react') },
17 | instrumenter: {
18 | '**/*.jsx': 'istanbul-react'
19 | },
20 | // ...
21 | }
22 | ```
23 |
24 | You can also just use it directly
25 |
26 | ```js
27 | var instrumenter = new require('istanbul-react').Instrumenter({});
28 |
29 | instrumenter.instrument(content, path, function(err, instrumentedCode) {
30 | // ...
31 | });
32 | ```
33 |
34 | ## Options
35 |
36 | You can use `modifyCodeBeforeInstrumentation` to modify code before instrumentation. It might be useful for example to get around chrome bug with [`'use strict';`](https://github.com/podio/istanbul-react/issues/3). It takes one argument, which give you an object, with two properties `code` - original code, `filename` - name of the file. This callback must return modified code as a string. In the example below you can see how `'use strict';` is prefixed with semi-colon to work around bug (or feature?) in chrome.
37 |
38 | ```js
39 | coverageReporter: {
40 | instrumenters: { 'istanbul-react' : require('istanbul-react') }
41 | instrumenter: {
42 | '**/*.jsx': 'istanbul-react'
43 | },
44 | instrumenterOptions: {
45 | 'istanbul-react': {
46 | modifyCodeBeforeInstrumentation: function fixChromeBugWithUseStrict(params) {
47 | return params.code.replace(/(['"]use strict['"];)/g, ';$1');
48 | }
49 | }
50 | },
51 | // ...
52 | }
53 | ```
54 |
55 | ## Tests
56 |
57 | ```sh
58 | $ npm test
59 | ```
60 |
61 | ## License
62 |
63 | MIT © [Podio](https://podio.com)
64 |
--------------------------------------------------------------------------------
/fixtures/react-es5.instrumented:
--------------------------------------------------------------------------------
1 |
2 | var __cov_TEhtOrl3pQbzvDff90Xx4g = (Function('return this'))();
3 | if (!__cov_TEhtOrl3pQbzvDff90Xx4g.__coverage__) { __cov_TEhtOrl3pQbzvDff90Xx4g.__coverage__ = {}; }
4 | __cov_TEhtOrl3pQbzvDff90Xx4g = __cov_TEhtOrl3pQbzvDff90Xx4g.__coverage__;
5 | if (!(__cov_TEhtOrl3pQbzvDff90Xx4g['fixtures/react-es5.jsx'])) {
6 | __cov_TEhtOrl3pQbzvDff90Xx4g['fixtures/react-es5.jsx'] = {"path":"fixtures/react-es5.jsx","s":{"1":0,"2":0},"b":{},"f":{"1":0},"fnMap":{"1":{"name":"(anonymous_1)","line":2,"loc":{"start":{"line":2,"column":10},"end":{"line":2,"column":21}}}},"statementMap":{"1":{"start":{"line":1,"column":-15},"end":{"line":5,"column":3}},"2":{"start":{"line":3,"column":4},"end":{"line":3,"column":71}}},"branchMap":{}};
7 | }
8 | __cov_TEhtOrl3pQbzvDff90Xx4g = __cov_TEhtOrl3pQbzvDff90Xx4g['fixtures/react-es5.jsx'];
9 | __cov_TEhtOrl3pQbzvDff90Xx4g.s['1']++;var HelloMessage=React.createClass({displayName:'HelloMessage',render:function(){__cov_TEhtOrl3pQbzvDff90Xx4g.f['1']++;__cov_TEhtOrl3pQbzvDff90Xx4g.s['2']++;return React.createElement('div',null,'Hello ',this.props.name);}});
10 |
--------------------------------------------------------------------------------
/fixtures/react-es5.jsx:
--------------------------------------------------------------------------------
1 | var HelloMessage = React.createClass({
2 | render: function() {
3 | return
Hello {this.props.name}
;
4 | }
5 | });
6 |
--------------------------------------------------------------------------------
/fixtures/react-es6-invalid.jsx:
--------------------------------------------------------------------------------
1 | class HelloMessage extendz React.Component {
2 | render() {
3 | return Hello {this.props.name}
;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/fixtures/react-es6.instrumented:
--------------------------------------------------------------------------------
1 |
2 | var __cov_FYUMjcew5i0vIy$Xc5RZOA = (Function('return this'))();
3 | if (!__cov_FYUMjcew5i0vIy$Xc5RZOA.__coverage__) { __cov_FYUMjcew5i0vIy$Xc5RZOA.__coverage__ = {}; }
4 | __cov_FYUMjcew5i0vIy$Xc5RZOA = __cov_FYUMjcew5i0vIy$Xc5RZOA.__coverage__;
5 | if (!(__cov_FYUMjcew5i0vIy$Xc5RZOA['fixtures/react-es6.jsx'])) {
6 | __cov_FYUMjcew5i0vIy$Xc5RZOA['fixtures/react-es6.jsx'] = {"path":"fixtures/react-es6.jsx","s":{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":1,"10":0,"11":0,"12":0,"13":0},"b":{"1":[0,0],"2":[0,0],"3":[0,0]},"f":{"1":0,"2":0},"fnMap":{"1":{"name":"HelloMessage","line":1,"loc":{"start":{"line":1,"column":403},"end":{"line":1,"column":426}}},"2":{"name":"(anonymous_2)","line":2,"loc":{"start":{"line":2,"column":95},"end":{"line":2,"column":106}}}},"statementMap":{"1":{"start":{"line":1,"column":-15},"end":{"line":1,"column":16}},"2":{"start":{"line":1,"column":16},"end":{"line":1,"column":170}},"3":{"start":{"line":1,"column":57},"end":{"line":1,"column":169}},"4":{"start":{"line":1,"column":106},"end":{"line":1,"column":168}},"5":{"start":{"line":1,"column":170},"end":{"line":1,"column":245}},"6":{"start":{"line":1,"column":245},"end":{"line":1,"column":310}},"7":{"start":{"line":1,"column":310},"end":{"line":1,"column":358}},"8":{"start":{"line":1,"column":358},"end":{"line":1,"column":403}},"9":{"start":{"line":1,"column":403},"end":{"line":1,"column":497}},"10":{"start":{"line":1,"column":440},"end":{"line":1,"column":496}},"11":{"start":{"line":1,"column":462},"end":{"line":1,"column":495}},"12":{"start":{"line":2,"column":2},"end":{"line":4,"column":6}},"13":{"start":{"line":3,"column":4},"end":{"line":3,"column":71}}},"branchMap":{"1":{"line":1,"type":"if","locations":[{"start":{"line":1,"column":57},"end":{"line":1,"column":57}},{"start":{"line":1,"column":57},"end":{"line":1,"column":57}}]},"2":{"line":1,"type":"cond-expr","locations":[{"start":{"line":1,"column":219},"end":{"line":1,"column":223}},{"start":{"line":1,"column":224},"end":{"line":1,"column":244}}]},"3":{"line":1,"type":"if","locations":[{"start":{"line":1,"column":440},"end":{"line":1,"column":440}},{"start":{"line":1,"column":440},"end":{"line":1,"column":440}}]}}};
7 | }
8 | __cov_FYUMjcew5i0vIy$Xc5RZOA = __cov_FYUMjcew5i0vIy$Xc5RZOA['fixtures/react-es6.jsx'];
9 | __cov_FYUMjcew5i0vIy$Xc5RZOA.s['1']++;var ____Class0=React.Component;__cov_FYUMjcew5i0vIy$Xc5RZOA.s['2']++;for(var ____Class0____Key in ____Class0){__cov_FYUMjcew5i0vIy$Xc5RZOA.s['3']++;if(____Class0.hasOwnProperty(____Class0____Key)){__cov_FYUMjcew5i0vIy$Xc5RZOA.b['1'][0]++;__cov_FYUMjcew5i0vIy$Xc5RZOA.s['4']++;HelloMessage[____Class0____Key]=____Class0[____Class0____Key];}else{__cov_FYUMjcew5i0vIy$Xc5RZOA.b['1'][1]++;}}__cov_FYUMjcew5i0vIy$Xc5RZOA.s['5']++;var ____SuperProtoOf____Class0=____Class0===null?(__cov_FYUMjcew5i0vIy$Xc5RZOA.b['2'][0]++,null):(__cov_FYUMjcew5i0vIy$Xc5RZOA.b['2'][1]++,____Class0.prototype);__cov_FYUMjcew5i0vIy$Xc5RZOA.s['6']++;HelloMessage.prototype=Object.create(____SuperProtoOf____Class0);__cov_FYUMjcew5i0vIy$Xc5RZOA.s['7']++;HelloMessage.prototype.constructor=HelloMessage;__cov_FYUMjcew5i0vIy$Xc5RZOA.s['8']++;HelloMessage.__superConstructor__=____Class0;function HelloMessage(){'use strict';__cov_FYUMjcew5i0vIy$Xc5RZOA.f['1']++;__cov_FYUMjcew5i0vIy$Xc5RZOA.s['10']++;if(____Class0!==null){__cov_FYUMjcew5i0vIy$Xc5RZOA.b['3'][0]++;__cov_FYUMjcew5i0vIy$Xc5RZOA.s['11']++;____Class0.apply(this,arguments);}else{__cov_FYUMjcew5i0vIy$Xc5RZOA.b['3'][1]++;}}__cov_FYUMjcew5i0vIy$Xc5RZOA.s['12']++;Object.defineProperty(HelloMessage.prototype,'render',{writable:true,configurable:true,value:function(){'use strict';__cov_FYUMjcew5i0vIy$Xc5RZOA.f['2']++;__cov_FYUMjcew5i0vIy$Xc5RZOA.s['13']++;return React.createElement('div',null,'Hello ',this.props.name);}});
10 |
--------------------------------------------------------------------------------
/fixtures/react-es6.jsx:
--------------------------------------------------------------------------------
1 | class HelloMessage extends React.Component {
2 | render() {
3 | return Hello {this.props.name}
;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var util = require('util');
4 |
5 | var istanbul = require('istanbul');
6 |
7 | var esprima = require('esprima');
8 | var reactTools = require('react-tools');
9 |
10 | function Instrumenter(opt){
11 | this.opt = opt || {};
12 | istanbul.Instrumenter.call(this, opt);
13 | }
14 |
15 | util.inherits(Instrumenter, istanbul.Instrumenter);
16 |
17 | Instrumenter.prototype._transform = function (content) {
18 | var transformed = reactTools.transformWithDetails(content, {
19 | sourceMap: true,
20 | harmony: true
21 | });
22 |
23 | var program = esprima.parse(transformed.code, {
24 | loc: true
25 | });
26 |
27 | return program;
28 | };
29 |
30 | Instrumenter.prototype._modifyBeforeInstrumentBasedOnOptions = function (params) {
31 | var modifyCodeBeforeInstrumentation = this.opt.modifyCodeBeforeInstrumentation;
32 | var code = params.code;
33 | if (modifyCodeBeforeInstrumentation) {
34 | code = modifyCodeBeforeInstrumentation(params);
35 | }
36 |
37 | return code;
38 | };
39 |
40 | Instrumenter.prototype.instrument = function (code, filename, callback) {
41 | code = this._modifyBeforeInstrumentBasedOnOptions({
42 | code: code,
43 | filename: filename
44 | });
45 | var program = this._transform(code);
46 |
47 | try {
48 | callback(void 0, this.instrumentASTSync(program, filename, code));
49 | } catch (exception) {
50 | callback(exception);
51 | }
52 | };
53 |
54 | Instrumenter.prototype.instrumentSync = function (code, filename) {
55 | code = this._modifyBeforeInstrumentBasedOnOptions({
56 | code: code,
57 | filename: filename
58 | });
59 | var program = this._transform(code);
60 |
61 | return this.instrumentASTSync(program, filename, code);
62 | };
63 |
64 | module.exports = {
65 | Instrumenter: Instrumenter
66 | };
67 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "istanbul-react",
3 | "version": "1.1.0",
4 | "description": "Instrumenter for 1:1 mapping of React JSX components",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git://github.com/podio/istanbul-react.git"
12 | },
13 | "keywords": [
14 | "istanbul",
15 | "react",
16 | "jsx",
17 | "instrumenter",
18 | "source",
19 | "map"
20 | ],
21 | "author": "Søren Brokær (http://podio.com)",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/podio/istanbul-react/issues"
25 | },
26 | "homepage": "https://github.com/podio/istanbul-react",
27 | "dependencies": {
28 | "react-tools": "^0.13.0",
29 | "esprima": "^2.1.0",
30 | "istanbul": "^0.3.8"
31 | },
32 | "devDependencies": {
33 | "eol": "^0.2.0",
34 | "mocha": "^2.2.1"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var fs = require('fs');
4 | var assert = require('assert');
5 | var eol = require('eol');
6 |
7 | var Instrumenter = require('./').Instrumenter;
8 |
9 | describe('istanbul-react', function(){
10 | it('should instrument es5 jsx', function(done) {
11 | var path = 'fixtures/react-es5.jsx';
12 |
13 | var jsx = fs.readFileSync(path, {encoding:'utf8'});
14 | var instrumented = fs.readFileSync(path.replace('jsx', 'instrumented'), {encoding:'utf8'});
15 |
16 | var instrumenter = new Instrumenter();
17 | instrumenter.instrument(jsx, path, function (err, instrumentedCode) {
18 | assert.equal(err, void 0);
19 | assert.equal(eol.auto(instrumentedCode), eol.auto(instrumented));
20 |
21 | done();
22 | })
23 | });
24 |
25 | it('should instrument es6 jsx', function(done) {
26 | var path = 'fixtures/react-es6.jsx';
27 |
28 | var jsx = fs.readFileSync(path, {encoding:'utf8'});
29 | var instrumented = fs.readFileSync(path.replace('jsx', 'instrumented'), {encoding:'utf8'});
30 |
31 | var instrumenter = new Instrumenter();
32 | instrumenter.instrument(jsx, path, function (err, instrumentedCode) {
33 | assert.equal(err, void 0);
34 | assert.equal(eol.auto(instrumentedCode), eol.auto(instrumented));
35 |
36 | done();
37 | })
38 | });
39 |
40 | it('should fail on instrumenting invalid es6 jsx', function(done) {
41 | var path = 'fixtures/react-es6-invalid.jsx';
42 |
43 | var jsx = fs.readFileSync(path, {encoding:'utf8'});
44 |
45 | try {
46 | var instrumenter = new Instrumenter();
47 | instrumenter.instrument(jsx, path, function (err, instrumentedCode) {
48 | assert.notEqual(err, void 0);
49 | assert.equal(instrumentedCode, void 0);
50 | });
51 | } catch(exception) {
52 | done();
53 | }
54 | });
55 |
56 | it('should instrument sync es6 jsx', function() {
57 | var path = 'fixtures/react-es5.jsx';
58 |
59 | var jsx = fs.readFileSync(path, {encoding:'utf8'});
60 | var instrumented = fs.readFileSync(path.replace('jsx', 'instrumented'), {encoding:'utf8'});
61 |
62 | var instrumenter = new Instrumenter();
63 | var instrumentedCode = instrumenter.instrumentSync(jsx, path);
64 |
65 | assert.equal(eol.auto(instrumentedCode), eol.auto(instrumented));
66 | });
67 |
68 | it('should modify code using options callback', function() {
69 | var code = [
70 | '"use strict";',
71 | 'var a = 10;'
72 | ].join('\n');
73 | var modifiedCode = [
74 | ';"use strict";',
75 | 'var a = 10;'
76 | ].join('\n');
77 | var instrumenter = new Instrumenter({
78 | modifyCodeBeforeInstrumentation: function modifyCodeBeforeInstrumentation(params) {
79 | return params.code.replace(/(['"]use strict['"];)/g, ';$1');
80 | }
81 | });
82 | var result = instrumenter._modifyBeforeInstrumentBasedOnOptions({
83 | code: code,
84 | filename: ''
85 | });
86 | assert.equal(modifiedCode, result);
87 | });
88 | });
89 |
--------------------------------------------------------------------------------