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:
parent
aae31ae24c
commit
2058ad99be
|
@ -29,23 +29,21 @@ describe('react displayName jsx', function() {
|
||||||
|
|
||||||
it('should only inject displayName if missing', function() {
|
it('should only inject displayName if missing', function() {
|
||||||
var code = [
|
var code = [
|
||||||
'/** @jsx React.DOM */',
|
|
||||||
'"use strict";',
|
'"use strict";',
|
||||||
'var Whateva = React.createClass({',
|
'var Whateva = React.createClass({',
|
||||||
' displayName: \'Whateva\',',
|
' displayName: \'Whateva\',',
|
||||||
' render: function() {',
|
' render: function() {',
|
||||||
' return <div className=\'whateva\'>...whateva.</div>;',
|
' return null;',
|
||||||
' }',
|
' }',
|
||||||
'});'
|
'});'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
var result = [
|
var result = [
|
||||||
'/** @jsx React.DOM */',
|
|
||||||
'"use strict";',
|
'"use strict";',
|
||||||
'var Whateva = React.createClass({',
|
'var Whateva = React.createClass({',
|
||||||
' displayName: \'Whateva\',',
|
' displayName: \'Whateva\',',
|
||||||
' render: function() {',
|
' render: function() {',
|
||||||
' return React.DOM.div({className: "whateva"}, "...whateva.");',
|
' return null;',
|
||||||
' }',
|
' }',
|
||||||
'});'
|
'});'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
@ -55,19 +53,17 @@ describe('react displayName jsx', function() {
|
||||||
|
|
||||||
it('should inject displayName in simple assignment', () => {
|
it('should inject displayName in simple assignment', () => {
|
||||||
var code = [
|
var code = [
|
||||||
'/** @jsx React.DOM */',
|
|
||||||
'var Component = React.createClass({',
|
'var Component = React.createClass({',
|
||||||
' render: function() {',
|
' render: function() {',
|
||||||
' return <div/>;',
|
' return null;',
|
||||||
' }',
|
' }',
|
||||||
'});'
|
'});'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
var result = [
|
var result = [
|
||||||
'/** @jsx React.DOM */',
|
|
||||||
'var Component = React.createClass({displayName: \'Component\',',
|
'var Component = React.createClass({displayName: \'Component\',',
|
||||||
' render: function() {',
|
' render: function() {',
|
||||||
' return React.DOM.div(null);',
|
' return null;',
|
||||||
' }',
|
' }',
|
||||||
'});'
|
'});'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
@ -77,21 +73,19 @@ describe('react displayName jsx', function() {
|
||||||
|
|
||||||
it('should inject displayName in simple assignment without var', () => {
|
it('should inject displayName in simple assignment without var', () => {
|
||||||
var code = [
|
var code = [
|
||||||
'/** @jsx React.DOM */',
|
|
||||||
'var Component;',
|
'var Component;',
|
||||||
'Component = React.createClass({',
|
'Component = React.createClass({',
|
||||||
' render: function() {',
|
' render: function() {',
|
||||||
' return <div/>;',
|
' return null;',
|
||||||
' }',
|
' }',
|
||||||
'});'
|
'});'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
var result = [
|
var result = [
|
||||||
'/** @jsx React.DOM */',
|
|
||||||
'var Component;',
|
'var Component;',
|
||||||
'Component = React.createClass({displayName: \'Component\',',
|
'Component = React.createClass({displayName: \'Component\',',
|
||||||
' render: function() {',
|
' render: function() {',
|
||||||
' return React.DOM.div(null);',
|
' return null;',
|
||||||
' }',
|
' }',
|
||||||
'});'
|
'});'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
@ -101,19 +95,17 @@ describe('react displayName jsx', function() {
|
||||||
|
|
||||||
it('should inject displayName in property assignment', () => {
|
it('should inject displayName in property assignment', () => {
|
||||||
var code = [
|
var code = [
|
||||||
'/** @jsx React.DOM */',
|
|
||||||
'exports.Component = React.createClass({',
|
'exports.Component = React.createClass({',
|
||||||
' render: function() {',
|
' render: function() {',
|
||||||
' return <div/>;',
|
' return null;',
|
||||||
' }',
|
' }',
|
||||||
'});'
|
'});'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
var result = [
|
var result = [
|
||||||
'/** @jsx React.DOM */',
|
|
||||||
'exports.Component = React.createClass({displayName: \'Component\',',
|
'exports.Component = React.createClass({displayName: \'Component\',',
|
||||||
' render: function() {',
|
' render: function() {',
|
||||||
' return React.DOM.div(null);',
|
' return null;',
|
||||||
' }',
|
' }',
|
||||||
'});'
|
'});'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
@ -123,22 +115,20 @@ describe('react displayName jsx', function() {
|
||||||
|
|
||||||
it('should inject displayName in object declaration', () => {
|
it('should inject displayName in object declaration', () => {
|
||||||
var code = [
|
var code = [
|
||||||
'/** @jsx React.DOM */',
|
|
||||||
'exports = {',
|
'exports = {',
|
||||||
' Component: React.createClass({',
|
' Component: React.createClass({',
|
||||||
' render: function() {',
|
' render: function() {',
|
||||||
' return <div/>;',
|
' return null;',
|
||||||
' }',
|
' }',
|
||||||
' })',
|
' })',
|
||||||
'};'
|
'};'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
var result = [
|
var result = [
|
||||||
'/** @jsx React.DOM */',
|
|
||||||
'exports = {',
|
'exports = {',
|
||||||
' Component: React.createClass({displayName: \'Component\',',
|
' Component: React.createClass({displayName: \'Component\',',
|
||||||
' render: function() {',
|
' render: function() {',
|
||||||
' return React.DOM.div(null);',
|
' return null;',
|
||||||
' }',
|
' }',
|
||||||
' })',
|
' })',
|
||||||
'};'
|
'};'
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
*
|
*
|
||||||
* @emails jeffmo@fb.com
|
* @emails jeffmo@fb.com
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*jshint evil:true, unused:false*/
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
require('mock-modules').autoMockOff();
|
require('mock-modules').autoMockOff();
|
||||||
|
@ -42,20 +45,19 @@ describe('react jsx', function() {
|
||||||
var y = 789012;
|
var y = 789012;
|
||||||
var z = 345678;
|
var z = 345678;
|
||||||
|
|
||||||
var HEADER =
|
|
||||||
'/**\n' +
|
|
||||||
' * @jsx React.DOM\n' +
|
|
||||||
' */\n';
|
|
||||||
|
|
||||||
var expectObjectAssign = function(code) {
|
var expectObjectAssign = function(code) {
|
||||||
var Component = jest.genMockFunction();
|
var Component = jest.genMockFunction();
|
||||||
var Child = jest.genMockFunction();
|
var Child = jest.genMockFunction();
|
||||||
var objectAssignMock = jest.genMockFunction();
|
var objectAssignMock = jest.genMockFunction();
|
||||||
Object.assign = objectAssignMock;
|
Object.assign = objectAssignMock;
|
||||||
eval(transform(HEADER + code).code);
|
eval(transform(code).code);
|
||||||
return expect(objectAssignMock);
|
return expect(objectAssignMock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var React = {
|
||||||
|
createElement: jest.genMockFunction()
|
||||||
|
};
|
||||||
|
|
||||||
it('should convert simple tags', function() {
|
it('should convert simple tags', function() {
|
||||||
var code = [
|
var code = [
|
||||||
'/**@jsx React.DOM*/',
|
'/**@jsx React.DOM*/',
|
||||||
|
@ -63,7 +65,7 @@ describe('react jsx', function() {
|
||||||
].join('\n');
|
].join('\n');
|
||||||
var result = [
|
var result = [
|
||||||
'/**@jsx React.DOM*/',
|
'/**@jsx React.DOM*/',
|
||||||
'var x = React.DOM.div(null);'
|
'var x = React.createElement(React.DOM.div, null);'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
expect(transform(code).code).toEqual(result);
|
expect(transform(code).code).toEqual(result);
|
||||||
|
@ -76,7 +78,7 @@ describe('react jsx', function() {
|
||||||
].join('\n');
|
].join('\n');
|
||||||
var result = [
|
var result = [
|
||||||
'/**@jsx React.DOM*/',
|
'/**@jsx React.DOM*/',
|
||||||
'var x = React.DOM.div(null, "text");'
|
'var x = React.createElement(React.DOM.div, null, "text");'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
expect(transform(code).code).toEqual(result);
|
expect(transform(code).code).toEqual(result);
|
||||||
|
@ -93,10 +95,12 @@ describe('react jsx', function() {
|
||||||
].join('\n');
|
].join('\n');
|
||||||
var result = [
|
var result = [
|
||||||
'/**@jsx React.DOM*/',
|
'/**@jsx React.DOM*/',
|
||||||
'var x = React.DOM.div(null, ',
|
'var x = React.createElement(React.DOM.div, null, ',
|
||||||
' React.DOM.div(null, React.DOM.br(null)), ',
|
' React.createElement(React.DOM.div, null, ' +
|
||||||
' Component(null, foo, React.DOM.br(null), bar), ',
|
'React.createElement(React.DOM.br, null)), ',
|
||||||
' React.DOM.br(null)',
|
' React.createElement(Component, null, foo, ' +
|
||||||
|
'React.createElement(React.DOM.br, null), bar), ',
|
||||||
|
' React.createElement(React.DOM.br, null)',
|
||||||
');'
|
');'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
|
@ -113,8 +117,8 @@ describe('react jsx', function() {
|
||||||
].join('\n');
|
].join('\n');
|
||||||
var result = [
|
var result = [
|
||||||
'/**@jsx React.DOM*/',
|
'/**@jsx React.DOM*/',
|
||||||
'var x = React.DOM.div(null, ',
|
'var x = React.createElement(React.DOM.div, null, ',
|
||||||
' Component(null)',
|
' React.createElement(Component, null)',
|
||||||
');'
|
');'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
|
@ -129,7 +133,7 @@ describe('react jsx', function() {
|
||||||
].join('\n');
|
].join('\n');
|
||||||
result = [
|
result = [
|
||||||
'/**@jsx React.DOM*/',
|
'/**@jsx React.DOM*/',
|
||||||
'var x = React.DOM.div(null, ',
|
'var x = React.createElement(React.DOM.div, null, ',
|
||||||
' this.props.children',
|
' this.props.children',
|
||||||
');'
|
');'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
@ -144,7 +148,7 @@ describe('react jsx', function() {
|
||||||
].join('\n');
|
].join('\n');
|
||||||
result = [
|
result = [
|
||||||
'/**@jsx React.DOM*/',
|
'/**@jsx React.DOM*/',
|
||||||
'var x = Composite(null, ',
|
'var x = React.createElement(Composite, null, ',
|
||||||
' this.props.children',
|
' this.props.children',
|
||||||
');'
|
');'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
@ -159,8 +163,8 @@ describe('react jsx', function() {
|
||||||
].join('\n');
|
].join('\n');
|
||||||
result = [
|
result = [
|
||||||
'/**@jsx React.DOM*/',
|
'/**@jsx React.DOM*/',
|
||||||
'var x = Composite(null, ',
|
'var x = React.createElement(Composite, null, ',
|
||||||
' Composite2(null)',
|
' React.createElement(Composite2, null)',
|
||||||
');'
|
');'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
expect(transform(code).code).toEqual(result);
|
expect(transform(code).code).toEqual(result);
|
||||||
|
@ -190,7 +194,7 @@ describe('react jsx', function() {
|
||||||
var result = [
|
var result = [
|
||||||
'/**@jsx React.DOM*/',
|
'/**@jsx React.DOM*/',
|
||||||
'var x =',
|
'var x =',
|
||||||
' React.DOM.div({',
|
' React.createElement(React.DOM.div, {',
|
||||||
' attr1: ',
|
' attr1: ',
|
||||||
' "foo" + "bar", ',
|
' "foo" + "bar", ',
|
||||||
' ',
|
' ',
|
||||||
|
@ -235,14 +239,14 @@ describe('react jsx', function() {
|
||||||
' * @jsx React.DOM',
|
' * @jsx React.DOM',
|
||||||
' */',
|
' */',
|
||||||
'var x = (',
|
'var x = (',
|
||||||
' React.DOM.div(null, ',
|
' React.createElement(React.DOM.div, null, ',
|
||||||
' /* A comment at the beginning */',
|
' /* A comment at the beginning */',
|
||||||
' /* A second comment at the beginning */',
|
' /* A second comment at the beginning */',
|
||||||
' React.DOM.span(null',
|
' React.createElement(React.DOM.span, null',
|
||||||
' /* A nested comment */',
|
' /* A nested comment */',
|
||||||
' ), ',
|
' ), ',
|
||||||
' /* A sandwiched comment */',
|
' /* A sandwiched comment */',
|
||||||
' React.DOM.br(null)',
|
' React.createElement(React.DOM.br, null)',
|
||||||
' /* A comment at the end */',
|
' /* A comment at the end */',
|
||||||
' /* A second comment at the end */',
|
' /* A second comment at the end */',
|
||||||
' )',
|
' )',
|
||||||
|
@ -273,11 +277,11 @@ describe('react jsx', function() {
|
||||||
' * @jsx React.DOM',
|
' * @jsx React.DOM',
|
||||||
' */',
|
' */',
|
||||||
'var x = (',
|
'var x = (',
|
||||||
' React.DOM.div({',
|
' React.createElement(React.DOM.div, {',
|
||||||
' /* a multi-line',
|
' /* a multi-line',
|
||||||
' comment */',
|
' comment */',
|
||||||
' attr1: "foo"}, ',
|
' attr1: "foo"}, ',
|
||||||
' React.DOM.span({// a double-slash comment',
|
' React.createElement(React.DOM.span, {// a double-slash comment',
|
||||||
' attr2: "bar"}',
|
' attr2: "bar"}',
|
||||||
' )',
|
' )',
|
||||||
' )',
|
' )',
|
||||||
|
@ -298,7 +302,7 @@ describe('react jsx', function() {
|
||||||
'/**',
|
'/**',
|
||||||
' * @jsx React.DOM',
|
' * @jsx React.DOM',
|
||||||
' */',
|
' */',
|
||||||
'React.DOM.div(null, "\u00A0");'
|
'React.createElement(React.DOM.div, null, "\u00A0");'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
expect(transform(code).code).toBe(result);
|
expect(transform(code).code).toBe(result);
|
||||||
|
@ -315,59 +319,29 @@ describe('react jsx', function() {
|
||||||
'/**',
|
'/**',
|
||||||
' * @jsx React.DOM',
|
' * @jsx React.DOM',
|
||||||
' */',
|
' */',
|
||||||
'React.DOM.div(null, "\u00A0 ");'
|
'React.createElement(React.DOM.div, null, "\u00A0 ");'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
expect(transform(code).code).toBe(result);
|
expect(transform(code).code).toBe(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle hasOwnProperty correctly', function() {
|
it('should handle hasOwnProperty correctly', function() {
|
||||||
var code = [
|
var code = '<hasOwnProperty>testing</hasOwnProperty>;';
|
||||||
'/**',
|
var result = 'React.createElement(hasOwnProperty, null, "testing");';
|
||||||
' * @jsx React.DOM',
|
|
||||||
' */',
|
|
||||||
'<hasOwnProperty>testing</hasOwnProperty>;'
|
|
||||||
].join('\n');
|
|
||||||
var result = [
|
|
||||||
'/**',
|
|
||||||
' * @jsx React.DOM',
|
|
||||||
' */',
|
|
||||||
'hasOwnProperty(null, "testing");'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
expect(transform(code).code).toBe(result);
|
expect(transform(code).code).toBe(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow constructor as prop', function() {
|
it('should allow constructor as prop', function() {
|
||||||
var code = [
|
var code = '<Component constructor="foo" />;';
|
||||||
'/**',
|
var result = 'React.createElement(Component, {constructor: "foo"});';
|
||||||
' * @jsx React.DOM',
|
|
||||||
' */',
|
|
||||||
'<Component constructor="foo" />;'
|
|
||||||
].join('\n');
|
|
||||||
var result = [
|
|
||||||
'/**',
|
|
||||||
' * @jsx React.DOM',
|
|
||||||
' */',
|
|
||||||
'Component({constructor: "foo"});'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
expect(transform(code).code).toBe(result);
|
expect(transform(code).code).toBe(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow JS namespacing', function() {
|
it('should allow JS namespacing', function() {
|
||||||
var code = [
|
var code = '<Namespace.Component />;';
|
||||||
'/**',
|
var result = 'React.createElement(Namespace.Component, null);';
|
||||||
' * @jsx React.DOM',
|
|
||||||
' */',
|
|
||||||
'<Namespace.Component />;'
|
|
||||||
].join('\n');
|
|
||||||
var result = [
|
|
||||||
'/**',
|
|
||||||
' * @jsx React.DOM',
|
|
||||||
' */',
|
|
||||||
'Namespace.Component(null);'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
expect(transform(code).code).toBe(result);
|
expect(transform(code).code).toBe(result);
|
||||||
});
|
});
|
||||||
|
@ -383,7 +357,7 @@ describe('react jsx', function() {
|
||||||
'/**',
|
'/**',
|
||||||
' * @jsx React.DOM',
|
' * @jsx React.DOM',
|
||||||
' */',
|
' */',
|
||||||
'Namespace.DeepNamespace.Component(null);'
|
'React.createElement(Namespace.DeepNamespace.Component, null);'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
expect(transform(code).code).toBe(result);
|
expect(transform(code).code).toBe(result);
|
||||||
|
@ -401,10 +375,12 @@ describe('react jsx', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('wraps props in Object.assign for spread attributes', function() {
|
it('wraps props in Object.assign for spread attributes', function() {
|
||||||
var code = HEADER +
|
var code =
|
||||||
'<Component { ... x } y\n={2 } z />';
|
'<Component { ... x } y\n' +
|
||||||
var result = HEADER +
|
'={2 } z />';
|
||||||
'Component(Object.assign({}, x , {y: \n2, z: true}))';
|
var result =
|
||||||
|
'React.createElement(Component, Object.assign({}, x , {y: \n' +
|
||||||
|
'2, z: true}))';
|
||||||
|
|
||||||
expect(transform(code).code).toBe(result);
|
expect(transform(code).code).toBe(result);
|
||||||
});
|
});
|
||||||
|
@ -420,7 +396,7 @@ describe('react jsx', function() {
|
||||||
'/**',
|
'/**',
|
||||||
' * @jsx React.DOM',
|
' * @jsx React.DOM',
|
||||||
' */',
|
' */',
|
||||||
'React.DOM[\'font-face\'](null);'
|
'React.createElement(React.DOM[\'font-face\'], null);'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
expect(transform(code).code).toBe(result);
|
expect(transform(code).code).toBe(result);
|
||||||
|
|
|
@ -69,6 +69,13 @@ function visitReactTag(traverse, object, path, state) {
|
||||||
throw new Error('Namespace tags are not supported. ReactJSX is not XML.');
|
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
|
// Only identifiers can be fallback tags or need quoting. We don't need to
|
||||||
// handle quoting for other types.
|
// handle quoting for other types.
|
||||||
var didAddTag = false;
|
var didAddTag = false;
|
||||||
|
@ -106,7 +113,11 @@ function visitReactTag(traverse, object, path, state) {
|
||||||
utils.catchup(nameObject.range[1], state);
|
utils.catchup(nameObject.range[1], state);
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.append('(', state);
|
if (isReact) {
|
||||||
|
utils.append(', ', state);
|
||||||
|
} else {
|
||||||
|
utils.append('(', state);
|
||||||
|
}
|
||||||
|
|
||||||
var hasAttributes = attributesObject.length;
|
var hasAttributes = attributesObject.length;
|
||||||
|
|
||||||
|
@ -278,9 +289,7 @@ function visitReactTag(traverse, object, path, state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
visitReactTag.test = function(object, path, state) {
|
visitReactTag.test = function(object, path, state) {
|
||||||
// only run react when react @jsx namespace is specified in docblock
|
return object.type === Syntax.XJSElement;
|
||||||
var jsx = utils.getDocblock(state).jsx;
|
|
||||||
return object.type === Syntax.XJSElement && jsx && jsx.length;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.visitorList = [
|
exports.visitorList = [
|
||||||
|
|
|
@ -87,19 +87,12 @@ function visitReactDisplayName(traverse, object, path, state) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Will only run on @jsx files for now.
|
|
||||||
*/
|
|
||||||
visitReactDisplayName.test = function(object, path, state) {
|
visitReactDisplayName.test = function(object, path, state) {
|
||||||
if (utils.getDocblock(state).jsx) {
|
return (
|
||||||
return (
|
object.type === Syntax.AssignmentExpression ||
|
||||||
object.type === Syntax.AssignmentExpression ||
|
object.type === Syntax.Property ||
|
||||||
object.type === Syntax.Property ||
|
object.type === Syntax.VariableDeclarator
|
||||||
object.type === Syntax.VariableDeclarator
|
);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.visitorList = [
|
exports.visitorList = [
|
||||||
|
|
Loading…
Reference in New Issue