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.
This commit is contained in:
Paul O’Shannessy 2014-08-20 00:24:26 -07:00
parent aae31ae24c
commit 2058ad99be
4 changed files with 72 additions and 104 deletions

View File

@ -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 <div className=\'whateva\'>...whateva.</div>;',
' 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 <div/>;',
' 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 <div/>;',
' 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 <div/>;',
' 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 <div/>;',
' return null;',
' }',
' })',
'};'
].join('\n');
var result = [
'/** @jsx React.DOM */',
'exports = {',
' Component: React.createClass({displayName: \'Component\',',
' render: function() {',
' return React.DOM.div(null);',
' return null;',
' }',
' })',
'};'

View File

@ -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',
' */',
'<hasOwnProperty>testing</hasOwnProperty>;'
].join('\n');
var result = [
'/**',
' * @jsx React.DOM',
' */',
'hasOwnProperty(null, "testing");'
].join('\n');
var code = '<hasOwnProperty>testing</hasOwnProperty>;';
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',
' */',
'<Component constructor="foo" />;'
].join('\n');
var result = [
'/**',
' * @jsx React.DOM',
' */',
'Component({constructor: "foo"});'
].join('\n');
var code = '<Component constructor="foo" />;';
var result = 'React.createElement(Component, {constructor: "foo"});';
expect(transform(code).code).toBe(result);
});
it('should allow JS namespacing', function() {
var code = [
'/**',
' * @jsx React.DOM',
' */',
'<Namespace.Component />;'
].join('\n');
var result = [
'/**',
' * @jsx React.DOM',
' */',
'Namespace.Component(null);'
].join('\n');
var code = '<Namespace.Component />;';
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 +
'<Component { ... x } y\n={2 } z />';
var result = HEADER +
'Component(Object.assign({}, x , {y: \n2, z: true}))';
var code =
'<Component { ... x } y\n' +
'={2 } z />';
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);

View File

@ -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 = [

View File

@ -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 = [