├── .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 [![Build Status](http://img.shields.io/travis/podio/istanbul-react.svg?style=flat-square)](https://travis-ci.org/podio/istanbul-react) [![Dependency Status](http://img.shields.io/gemnasium/podio/istanbul-react.svg?style=flat-square)](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 | --------------------------------------------------------------------------------