From 2058ad99be59c986a534160f4897d27a1d455a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20O=E2=80=99Shannessy?= Date: Wed, 20 Aug 2014 00:24:26 -0700 Subject: [PATCH] Sync out latest JSX transforms The biggest change: instead of calling functions directly, we now use React.createElement to wrap. The other big change: this removes the need for the @jsx pragma. Instead we'll parse everything, assume React is in scope, and only look for the actual XJS parts in the AST (as opposed to only runing the transform when @jsx was specified in the docblock). Note: this is actually a series of commits internally and should have been syned out in pieces over the past several weeks. --- .../__tests__/react-displayName-test.js | 30 ++--- .../transforms/__tests__/react-test.js | 112 +++++++----------- vendor/fbtransform/transforms/react.js | 17 ++- .../transforms/reactDisplayName.js | 17 +-- 4 files changed, 72 insertions(+), 104 deletions(-) diff --git a/vendor/fbtransform/transforms/__tests__/react-displayName-test.js b/vendor/fbtransform/transforms/__tests__/react-displayName-test.js index 648e41f59e..b4bab7a78c 100644 --- a/vendor/fbtransform/transforms/__tests__/react-displayName-test.js +++ b/vendor/fbtransform/transforms/__tests__/react-displayName-test.js @@ -29,23 +29,21 @@ describe('react displayName jsx', function() { it('should only inject displayName if missing', function() { var code = [ - '/** @jsx React.DOM */', '"use strict";', 'var Whateva = React.createClass({', ' displayName: \'Whateva\',', ' render: function() {', - ' return
...whateva.
;', + ' return null;', ' }', '});' ].join('\n'); var result = [ - '/** @jsx React.DOM */', '"use strict";', 'var Whateva = React.createClass({', ' displayName: \'Whateva\',', ' render: function() {', - ' return React.DOM.div({className: "whateva"}, "...whateva.");', + ' return null;', ' }', '});' ].join('\n'); @@ -55,19 +53,17 @@ describe('react displayName jsx', function() { it('should inject displayName in simple assignment', () => { var code = [ - '/** @jsx React.DOM */', 'var Component = React.createClass({', ' render: function() {', - ' return
;', + ' return null;', ' }', '});' ].join('\n'); var result = [ - '/** @jsx React.DOM */', 'var Component = React.createClass({displayName: \'Component\',', ' render: function() {', - ' return React.DOM.div(null);', + ' return null;', ' }', '});' ].join('\n'); @@ -77,21 +73,19 @@ describe('react displayName jsx', function() { it('should inject displayName in simple assignment without var', () => { var code = [ - '/** @jsx React.DOM */', 'var Component;', 'Component = React.createClass({', ' render: function() {', - ' return
;', + ' return null;', ' }', '});' ].join('\n'); var result = [ - '/** @jsx React.DOM */', 'var Component;', 'Component = React.createClass({displayName: \'Component\',', ' render: function() {', - ' return React.DOM.div(null);', + ' return null;', ' }', '});' ].join('\n'); @@ -101,19 +95,17 @@ describe('react displayName jsx', function() { it('should inject displayName in property assignment', () => { var code = [ - '/** @jsx React.DOM */', 'exports.Component = React.createClass({', ' render: function() {', - ' return
;', + ' return null;', ' }', '});' ].join('\n'); var result = [ - '/** @jsx React.DOM */', 'exports.Component = React.createClass({displayName: \'Component\',', ' render: function() {', - ' return React.DOM.div(null);', + ' return null;', ' }', '});' ].join('\n'); @@ -123,22 +115,20 @@ describe('react displayName jsx', function() { it('should inject displayName in object declaration', () => { var code = [ - '/** @jsx React.DOM */', 'exports = {', ' Component: React.createClass({', ' render: function() {', - ' return
;', + ' return null;', ' }', ' })', '};' ].join('\n'); var result = [ - '/** @jsx React.DOM */', 'exports = {', ' Component: React.createClass({displayName: \'Component\',', ' render: function() {', - ' return React.DOM.div(null);', + ' return null;', ' }', ' })', '};' diff --git a/vendor/fbtransform/transforms/__tests__/react-test.js b/vendor/fbtransform/transforms/__tests__/react-test.js index 005c9a73e1..82f7856904 100644 --- a/vendor/fbtransform/transforms/__tests__/react-test.js +++ b/vendor/fbtransform/transforms/__tests__/react-test.js @@ -15,6 +15,9 @@ * * @emails jeffmo@fb.com */ + +/*jshint evil:true, unused:false*/ + "use strict"; require('mock-modules').autoMockOff(); @@ -42,20 +45,19 @@ describe('react jsx', function() { var y = 789012; var z = 345678; - var HEADER = - '/**\n' + - ' * @jsx React.DOM\n' + - ' */\n'; - var expectObjectAssign = function(code) { var Component = jest.genMockFunction(); var Child = jest.genMockFunction(); var objectAssignMock = jest.genMockFunction(); Object.assign = objectAssignMock; - eval(transform(HEADER + code).code); + eval(transform(code).code); return expect(objectAssignMock); } + var React = { + createElement: jest.genMockFunction() + }; + it('should convert simple tags', function() { var code = [ '/**@jsx React.DOM*/', @@ -63,7 +65,7 @@ describe('react jsx', function() { ].join('\n'); var result = [ '/**@jsx React.DOM*/', - 'var x = React.DOM.div(null);' + 'var x = React.createElement(React.DOM.div, null);' ].join('\n'); expect(transform(code).code).toEqual(result); @@ -76,7 +78,7 @@ describe('react jsx', function() { ].join('\n'); var result = [ '/**@jsx React.DOM*/', - 'var x = React.DOM.div(null, "text");' + 'var x = React.createElement(React.DOM.div, null, "text");' ].join('\n'); expect(transform(code).code).toEqual(result); @@ -93,10 +95,12 @@ describe('react jsx', function() { ].join('\n'); var result = [ '/**@jsx React.DOM*/', - 'var x = React.DOM.div(null, ', - ' React.DOM.div(null, React.DOM.br(null)), ', - ' Component(null, foo, React.DOM.br(null), bar), ', - ' React.DOM.br(null)', + 'var x = React.createElement(React.DOM.div, null, ', + ' React.createElement(React.DOM.div, null, ' + + 'React.createElement(React.DOM.br, null)), ', + ' React.createElement(Component, null, foo, ' + + 'React.createElement(React.DOM.br, null), bar), ', + ' React.createElement(React.DOM.br, null)', ');' ].join('\n'); @@ -113,8 +117,8 @@ describe('react jsx', function() { ].join('\n'); var result = [ '/**@jsx React.DOM*/', - 'var x = React.DOM.div(null, ', - ' Component(null)', + 'var x = React.createElement(React.DOM.div, null, ', + ' React.createElement(Component, null)', ');' ].join('\n'); @@ -129,7 +133,7 @@ describe('react jsx', function() { ].join('\n'); result = [ '/**@jsx React.DOM*/', - 'var x = React.DOM.div(null, ', + 'var x = React.createElement(React.DOM.div, null, ', ' this.props.children', ');' ].join('\n'); @@ -144,7 +148,7 @@ describe('react jsx', function() { ].join('\n'); result = [ '/**@jsx React.DOM*/', - 'var x = Composite(null, ', + 'var x = React.createElement(Composite, null, ', ' this.props.children', ');' ].join('\n'); @@ -159,8 +163,8 @@ describe('react jsx', function() { ].join('\n'); result = [ '/**@jsx React.DOM*/', - 'var x = Composite(null, ', - ' Composite2(null)', + 'var x = React.createElement(Composite, null, ', + ' React.createElement(Composite2, null)', ');' ].join('\n'); expect(transform(code).code).toEqual(result); @@ -190,7 +194,7 @@ describe('react jsx', function() { var result = [ '/**@jsx React.DOM*/', 'var x =', - ' React.DOM.div({', + ' React.createElement(React.DOM.div, {', ' attr1: ', ' "foo" + "bar", ', ' ', @@ -235,14 +239,14 @@ describe('react jsx', function() { ' * @jsx React.DOM', ' */', 'var x = (', - ' React.DOM.div(null, ', + ' React.createElement(React.DOM.div, null, ', ' /* A comment at the beginning */', ' /* A second comment at the beginning */', - ' React.DOM.span(null', + ' React.createElement(React.DOM.span, null', ' /* A nested comment */', ' ), ', ' /* A sandwiched comment */', - ' React.DOM.br(null)', + ' React.createElement(React.DOM.br, null)', ' /* A comment at the end */', ' /* A second comment at the end */', ' )', @@ -273,11 +277,11 @@ describe('react jsx', function() { ' * @jsx React.DOM', ' */', 'var x = (', - ' React.DOM.div({', + ' React.createElement(React.DOM.div, {', ' /* a multi-line', ' comment */', ' attr1: "foo"}, ', - ' React.DOM.span({// a double-slash comment', + ' React.createElement(React.DOM.span, {// a double-slash comment', ' attr2: "bar"}', ' )', ' )', @@ -298,7 +302,7 @@ describe('react jsx', function() { '/**', ' * @jsx React.DOM', ' */', - 'React.DOM.div(null, "\u00A0");' + 'React.createElement(React.DOM.div, null, "\u00A0");' ].join('\n'); expect(transform(code).code).toBe(result); @@ -315,59 +319,29 @@ describe('react jsx', function() { '/**', ' * @jsx React.DOM', ' */', - 'React.DOM.div(null, "\u00A0 ");' + 'React.createElement(React.DOM.div, null, "\u00A0 ");' ].join('\n'); expect(transform(code).code).toBe(result); }); it('should handle hasOwnProperty correctly', function() { - var code = [ - '/**', - ' * @jsx React.DOM', - ' */', - 'testing;' - ].join('\n'); - var result = [ - '/**', - ' * @jsx React.DOM', - ' */', - 'hasOwnProperty(null, "testing");' - ].join('\n'); + var code = 'testing;'; + var result = 'React.createElement(hasOwnProperty, null, "testing");'; expect(transform(code).code).toBe(result); }); it('should allow constructor as prop', function() { - var code = [ - '/**', - ' * @jsx React.DOM', - ' */', - ';' - ].join('\n'); - var result = [ - '/**', - ' * @jsx React.DOM', - ' */', - 'Component({constructor: "foo"});' - ].join('\n'); + var code = ';'; + var result = 'React.createElement(Component, {constructor: "foo"});'; expect(transform(code).code).toBe(result); }); it('should allow JS namespacing', function() { - var code = [ - '/**', - ' * @jsx React.DOM', - ' */', - ';' - ].join('\n'); - var result = [ - '/**', - ' * @jsx React.DOM', - ' */', - 'Namespace.Component(null);' - ].join('\n'); + var code = ';'; + var result = 'React.createElement(Namespace.Component, null);'; expect(transform(code).code).toBe(result); }); @@ -383,7 +357,7 @@ describe('react jsx', function() { '/**', ' * @jsx React.DOM', ' */', - 'Namespace.DeepNamespace.Component(null);' + 'React.createElement(Namespace.DeepNamespace.Component, null);' ].join('\n'); expect(transform(code).code).toBe(result); @@ -401,10 +375,12 @@ describe('react jsx', function() { }); it('wraps props in Object.assign for spread attributes', function() { - var code = HEADER + - ''; - var result = HEADER + - 'Component(Object.assign({}, x , {y: \n2, z: true}))'; + var code = + ''; + var result = + 'React.createElement(Component, Object.assign({}, x , {y: \n' + + '2, z: true}))'; expect(transform(code).code).toBe(result); }); @@ -420,7 +396,7 @@ describe('react jsx', function() { '/**', ' * @jsx React.DOM', ' */', - 'React.DOM[\'font-face\'](null);' + 'React.createElement(React.DOM[\'font-face\'], null);' ].join('\n'); expect(transform(code).code).toBe(result); diff --git a/vendor/fbtransform/transforms/react.js b/vendor/fbtransform/transforms/react.js index 12aaa002f7..a99e552ce1 100644 --- a/vendor/fbtransform/transforms/react.js +++ b/vendor/fbtransform/transforms/react.js @@ -69,6 +69,13 @@ function visitReactTag(traverse, object, path, state) { throw new Error('Namespace tags are not supported. ReactJSX is not XML.'); } + var isReact = jsxObjIdent !== 'JSXDOM'; + + // We assume that the React runtime is already in scope + if (isReact) { + utils.append('React.createElement(', state); + } + // Only identifiers can be fallback tags or need quoting. We don't need to // handle quoting for other types. var didAddTag = false; @@ -106,7 +113,11 @@ function visitReactTag(traverse, object, path, state) { utils.catchup(nameObject.range[1], state); } - utils.append('(', state); + if (isReact) { + utils.append(', ', state); + } else { + utils.append('(', state); + } var hasAttributes = attributesObject.length; @@ -278,9 +289,7 @@ function visitReactTag(traverse, object, path, state) { } visitReactTag.test = function(object, path, state) { - // only run react when react @jsx namespace is specified in docblock - var jsx = utils.getDocblock(state).jsx; - return object.type === Syntax.XJSElement && jsx && jsx.length; + return object.type === Syntax.XJSElement; }; exports.visitorList = [ diff --git a/vendor/fbtransform/transforms/reactDisplayName.js b/vendor/fbtransform/transforms/reactDisplayName.js index 6bc784c6e0..7d974a0d43 100644 --- a/vendor/fbtransform/transforms/reactDisplayName.js +++ b/vendor/fbtransform/transforms/reactDisplayName.js @@ -87,19 +87,12 @@ function visitReactDisplayName(traverse, object, path, state) { } } -/** - * Will only run on @jsx files for now. - */ visitReactDisplayName.test = function(object, path, state) { - if (utils.getDocblock(state).jsx) { - return ( - object.type === Syntax.AssignmentExpression || - object.type === Syntax.Property || - object.type === Syntax.VariableDeclarator - ); - } else { - return false; - } + return ( + object.type === Syntax.AssignmentExpression || + object.type === Syntax.Property || + object.type === Syntax.VariableDeclarator + ); }; exports.visitorList = [