Update React JSX Transforms
* Extract JSXDOM into separate transform * Rename lower case variables in JSX to upper case * Drop React.DOM docblock, use string tags instead
This commit is contained in:
parent
1a1104f56f
commit
c4658c1728
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @emails jeffmo@fb.com
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
/*jshint evil:true, unused:false*/
|
||||
|
@ -59,34 +59,21 @@ describe('react jsx', function() {
|
|||
};
|
||||
|
||||
it('should convert simple tags', function() {
|
||||
var code = [
|
||||
'/**@jsx React.DOM*/',
|
||||
'var x = <div></div>;'
|
||||
].join('\n');
|
||||
var result = [
|
||||
'/**@jsx React.DOM*/',
|
||||
'var x = React.createElement(React.DOM.div, null);'
|
||||
].join('\n');
|
||||
var code = 'var x = <div></div>;';
|
||||
var result = 'var x = React.createElement("div", null);';
|
||||
|
||||
expect(transform(code).code).toEqual(result);
|
||||
});
|
||||
|
||||
it('should convert simple text', function() {
|
||||
var code = [
|
||||
'/**@jsx React.DOM*/\n' +
|
||||
'var x = <div>text</div>;'
|
||||
].join('\n');
|
||||
var result = [
|
||||
'/**@jsx React.DOM*/',
|
||||
'var x = React.createElement(React.DOM.div, null, "text");'
|
||||
].join('\n');
|
||||
var code = 'var x = <div>text</div>;';
|
||||
var result = 'var x = React.createElement("div", null, "text");';
|
||||
|
||||
expect(transform(code).code).toEqual(result);
|
||||
});
|
||||
|
||||
it('should have correct comma in nested children', function() {
|
||||
var code = [
|
||||
'/**@jsx React.DOM*/',
|
||||
'var x = <div>',
|
||||
' <div><br /></div>',
|
||||
' <Component>{foo}<br />{bar}</Component>',
|
||||
|
@ -94,13 +81,12 @@ describe('react jsx', function() {
|
|||
'</div>;'
|
||||
].join('\n');
|
||||
var result = [
|
||||
'/**@jsx React.DOM*/',
|
||||
'var x = React.createElement(React.DOM.div, null, ',
|
||||
' React.createElement(React.DOM.div, null, ' +
|
||||
'React.createElement(React.DOM.br, null)), ',
|
||||
'var x = React.createElement("div", null, ',
|
||||
' React.createElement("div", null, ' +
|
||||
'React.createElement("br", null)), ',
|
||||
' React.createElement(Component, null, foo, ' +
|
||||
'React.createElement(React.DOM.br, null), bar), ',
|
||||
' React.createElement(React.DOM.br, null)',
|
||||
'React.createElement("br", null), bar), ',
|
||||
' React.createElement("br", null)',
|
||||
');'
|
||||
].join('\n');
|
||||
|
||||
|
@ -110,14 +96,12 @@ describe('react jsx', function() {
|
|||
it('should avoid wrapping in extra parens if not needed', function() {
|
||||
// Try with a single composite child, wrapped in a div.
|
||||
var code = [
|
||||
'/**@jsx React.DOM*/',
|
||||
'var x = <div>',
|
||||
' <Component />',
|
||||
'</div>;'
|
||||
].join('\n');
|
||||
var result = [
|
||||
'/**@jsx React.DOM*/',
|
||||
'var x = React.createElement(React.DOM.div, null, ',
|
||||
'var x = React.createElement("div", null, ',
|
||||
' React.createElement(Component, null)',
|
||||
');'
|
||||
].join('\n');
|
||||
|
@ -126,14 +110,12 @@ describe('react jsx', function() {
|
|||
|
||||
// Try with a single interpolated child, wrapped in a div.
|
||||
code = [
|
||||
'/**@jsx React.DOM*/',
|
||||
'var x = <div>',
|
||||
' {this.props.children}',
|
||||
'</div>;'
|
||||
].join('\n');
|
||||
result = [
|
||||
'/**@jsx React.DOM*/',
|
||||
'var x = React.createElement(React.DOM.div, null, ',
|
||||
'var x = React.createElement("div", null, ',
|
||||
' this.props.children',
|
||||
');'
|
||||
].join('\n');
|
||||
|
@ -141,13 +123,11 @@ describe('react jsx', function() {
|
|||
|
||||
// Try with a single interpolated child, wrapped in a composite.
|
||||
code = [
|
||||
'/**@jsx React.DOM*/',
|
||||
'var x = <Composite>',
|
||||
' {this.props.children}',
|
||||
'</Composite>;'
|
||||
].join('\n');
|
||||
result = [
|
||||
'/**@jsx React.DOM*/',
|
||||
'var x = React.createElement(Composite, null, ',
|
||||
' this.props.children',
|
||||
');'
|
||||
|
@ -156,13 +136,11 @@ describe('react jsx', function() {
|
|||
|
||||
// Try with a single composite child, wrapped in a composite.
|
||||
code = [
|
||||
'/**@jsx React.DOM*/',
|
||||
'var x = <Composite>',
|
||||
' <Composite2 />',
|
||||
'</Composite>;'
|
||||
].join('\n');
|
||||
result = [
|
||||
'/**@jsx React.DOM*/',
|
||||
'var x = React.createElement(Composite, null, ',
|
||||
' React.createElement(Composite2, null)',
|
||||
');'
|
||||
|
@ -172,7 +150,6 @@ describe('react jsx', function() {
|
|||
|
||||
it('should insert commas after expressions before whitespace', function() {
|
||||
var code = [
|
||||
'/**@jsx React.DOM*/',
|
||||
'var x =',
|
||||
' <div',
|
||||
' attr1={',
|
||||
|
@ -192,9 +169,8 @@ describe('react jsx', function() {
|
|||
' </div>;'
|
||||
].join('\n');
|
||||
var result = [
|
||||
'/**@jsx React.DOM*/',
|
||||
'var x =',
|
||||
' React.createElement(React.DOM.div, {',
|
||||
' React.createElement("div", {',
|
||||
' attr1: ',
|
||||
' "foo" + "bar", ',
|
||||
' ',
|
||||
|
@ -217,9 +193,6 @@ describe('react jsx', function() {
|
|||
|
||||
it('should properly handle comments adjacent to children', function() {
|
||||
var code = [
|
||||
'/**',
|
||||
' * @jsx React.DOM',
|
||||
' */',
|
||||
'var x = (',
|
||||
' <div>',
|
||||
' {/* A comment at the beginning */}',
|
||||
|
@ -235,18 +208,15 @@ describe('react jsx', function() {
|
|||
');'
|
||||
].join('\n');
|
||||
var result = [
|
||||
'/**',
|
||||
' * @jsx React.DOM',
|
||||
' */',
|
||||
'var x = (',
|
||||
' React.createElement(React.DOM.div, null, ',
|
||||
' React.createElement("div", null, ',
|
||||
' /* A comment at the beginning */',
|
||||
' /* A second comment at the beginning */',
|
||||
' React.createElement(React.DOM.span, null',
|
||||
' React.createElement("span", null',
|
||||
' /* A nested comment */',
|
||||
' ), ',
|
||||
' /* A sandwiched comment */',
|
||||
' React.createElement(React.DOM.br, null)',
|
||||
' React.createElement("br", null)',
|
||||
' /* A comment at the end */',
|
||||
' /* A second comment at the end */',
|
||||
' )',
|
||||
|
@ -258,9 +228,6 @@ describe('react jsx', function() {
|
|||
|
||||
it('should properly handle comments between props', function() {
|
||||
var code = [
|
||||
'/**',
|
||||
' * @jsx React.DOM',
|
||||
' */',
|
||||
'var x = (',
|
||||
' <div',
|
||||
' /* a multi-line',
|
||||
|
@ -273,15 +240,12 @@ describe('react jsx', function() {
|
|||
');'
|
||||
].join('\n');
|
||||
var result = [
|
||||
'/**',
|
||||
' * @jsx React.DOM',
|
||||
' */',
|
||||
'var x = (',
|
||||
' React.createElement(React.DOM.div, {',
|
||||
' React.createElement("div", {',
|
||||
' /* a multi-line',
|
||||
' comment */',
|
||||
' attr1: "foo"}, ',
|
||||
' React.createElement(React.DOM.span, {// a double-slash comment',
|
||||
' React.createElement("span", {// a double-slash comment',
|
||||
' attr2: "bar"}',
|
||||
' )',
|
||||
' )',
|
||||
|
@ -293,16 +257,10 @@ describe('react jsx', function() {
|
|||
|
||||
it('should not strip tags with a single child of ', function() {
|
||||
var code = [
|
||||
'/**',
|
||||
' * @jsx React.DOM',
|
||||
' */',
|
||||
'<div> </div>;'
|
||||
].join('\n');
|
||||
var result = [
|
||||
'/**',
|
||||
' * @jsx React.DOM',
|
||||
' */',
|
||||
'React.createElement(React.DOM.div, null, "\u00A0");'
|
||||
'React.createElement("div", null, "\u00A0");'
|
||||
].join('\n');
|
||||
|
||||
expect(transform(code).code).toBe(result);
|
||||
|
@ -310,16 +268,10 @@ describe('react jsx', function() {
|
|||
|
||||
it('should not strip even coupled with other whitespace', function() {
|
||||
var code = [
|
||||
'/**',
|
||||
' * @jsx React.DOM',
|
||||
' */',
|
||||
'<div> </div>;'
|
||||
].join('\n');
|
||||
var result = [
|
||||
'/**',
|
||||
' * @jsx React.DOM',
|
||||
' */',
|
||||
'React.createElement(React.DOM.div, null, "\u00A0 ");'
|
||||
'React.createElement("div", null, "\u00A0 ");'
|
||||
].join('\n');
|
||||
|
||||
expect(transform(code).code).toBe(result);
|
||||
|
@ -327,9 +279,13 @@ describe('react jsx', function() {
|
|||
|
||||
it('should handle hasOwnProperty correctly', function() {
|
||||
var code = '<hasOwnProperty>testing</hasOwnProperty>;';
|
||||
var result = 'React.createElement(hasOwnProperty, null, "testing");';
|
||||
// var result = 'React.createElement("hasOwnProperty", null, "testing");';
|
||||
|
||||
expect(transform(code).code).toBe(result);
|
||||
// expect(transform(code).code).toBe(result);
|
||||
|
||||
// This is currently not supported, and will generate a string tag in
|
||||
// a follow up.
|
||||
expect(() => transform(code)).toThrow();
|
||||
});
|
||||
|
||||
it('should allow constructor as prop', function() {
|
||||
|
@ -347,29 +303,15 @@ describe('react jsx', function() {
|
|||
});
|
||||
|
||||
it('should allow deeper JS namespacing', function() {
|
||||
var code = [
|
||||
'/**',
|
||||
' * @jsx React.DOM',
|
||||
' */',
|
||||
'<Namespace.DeepNamespace.Component />;'
|
||||
].join('\n');
|
||||
var result = [
|
||||
'/**',
|
||||
' * @jsx React.DOM',
|
||||
' */',
|
||||
'React.createElement(Namespace.DeepNamespace.Component, null);'
|
||||
].join('\n');
|
||||
var code = '<Namespace.DeepNamespace.Component />;';
|
||||
var result =
|
||||
'React.createElement(Namespace.DeepNamespace.Component, null);';
|
||||
|
||||
expect(transform(code).code).toBe(result);
|
||||
});
|
||||
|
||||
it('should disallow XML namespacing', function() {
|
||||
var code = [
|
||||
'/**',
|
||||
' * @jsx React.DOM',
|
||||
' */',
|
||||
'<Namespace:Component />;'
|
||||
].join('\n');
|
||||
var code = '<Namespace:Component />;';
|
||||
|
||||
expect(() => transform(code)).toThrow();
|
||||
});
|
||||
|
@ -399,18 +341,8 @@ describe('react jsx', function() {
|
|||
});
|
||||
|
||||
it('should transform known hyphenated tags', function() {
|
||||
var code = [
|
||||
'/**',
|
||||
' * @jsx React.DOM',
|
||||
' */',
|
||||
'<font-face />;'
|
||||
].join('\n');
|
||||
var result = [
|
||||
'/**',
|
||||
' * @jsx React.DOM',
|
||||
' */',
|
||||
'React.createElement(React.DOM[\'font-face\'], null);'
|
||||
].join('\n');
|
||||
var code = '<font-face />;';
|
||||
var result = 'React.createElement("font-face", null);';
|
||||
|
||||
expect(transform(code).code).toBe(result);
|
||||
});
|
||||
|
@ -422,12 +354,7 @@ describe('react jsx', function() {
|
|||
});
|
||||
|
||||
it('should throw for unknown hyphenated tags', function() {
|
||||
var code = [
|
||||
'/**',
|
||||
' * @jsx React.DOM',
|
||||
' */',
|
||||
'<x-component />;'
|
||||
].join('\n');
|
||||
var code = '<x-component />;';
|
||||
|
||||
expect(() => transform(code)).toThrow();
|
||||
});
|
||||
|
|
|
@ -28,27 +28,16 @@ var quoteAttrName = require('./xjs').quoteAttrName;
|
|||
var trimLeft = require('./xjs').trimLeft;
|
||||
|
||||
/**
|
||||
* Customized desugar processor.
|
||||
* Customized desugar processor for React JSX. Currently:
|
||||
*
|
||||
* Currently: (Somewhat tailored to React)
|
||||
* <X> </X> => X(null, null)
|
||||
* <X prop="1" /> => X({prop: '1'}, null)
|
||||
* <X prop="2"><Y /></X> => X({prop:'2'}, Y(null, null))
|
||||
* <X prop="2"><Y /><Z /></X> => X({prop:'2'}, [Y(null, null), Z(null, null)])
|
||||
*
|
||||
* Exceptions to the simple rules above:
|
||||
* if a property is named "class" it will be changed to "className" in the
|
||||
* javascript since "class" is not a valid object key in javascript.
|
||||
* <X> </X> => React.createElement(X, null)
|
||||
* <X prop="1" /> => React.createElement(X, {prop: '1'}, null)
|
||||
* <X prop="2"><Y /></X> => React.createElement(X, {prop:'2'},
|
||||
* React.createElement(Y, null)
|
||||
* )
|
||||
* <div /> => React.createElement("div", null)
|
||||
*/
|
||||
|
||||
var JSX_ATTRIBUTE_TRANSFORMS = {
|
||||
cxName: function(attr) {
|
||||
throw new Error(
|
||||
"cxName is no longer supported, use className={cx(...)} instead"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes all non-whitespace/parenthesis characters
|
||||
*/
|
||||
|
@ -57,8 +46,12 @@ function stripNonWhiteParen(value) {
|
|||
return value.replace(reNonWhiteParen, '');
|
||||
}
|
||||
|
||||
var tagConvention = /^[a-z]|\-/;
|
||||
function isTagName(name) {
|
||||
return tagConvention.test(name);
|
||||
}
|
||||
|
||||
function visitReactTag(traverse, object, path, state) {
|
||||
var jsxObjIdent = utils.getDocblock(state).jsx;
|
||||
var openingElement = object.openingElement;
|
||||
var nameObject = openingElement.name;
|
||||
var attributesObject = openingElement.attributes;
|
||||
|
@ -69,55 +62,31 @@ 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);
|
||||
}
|
||||
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;
|
||||
|
||||
// Only identifiers can be fallback tags. XJSMemberExpressions are not.
|
||||
if (nameObject.type === Syntax.XJSIdentifier) {
|
||||
var tagName = nameObject.name;
|
||||
var quotedTagName = quoteAttrName(tagName);
|
||||
|
||||
if (FALLBACK_TAGS.hasOwnProperty(tagName)) {
|
||||
// "Properly" handle invalid identifiers, like <font-face>, which needs to
|
||||
// be enclosed in quotes.
|
||||
var predicate =
|
||||
tagName === quotedTagName ?
|
||||
('.' + tagName) :
|
||||
('[' + quotedTagName + ']');
|
||||
utils.append(jsxObjIdent + predicate, state);
|
||||
utils.move(nameObject.range[1], state);
|
||||
didAddTag = true;
|
||||
} else if (tagName !== quotedTagName) {
|
||||
// If we're in the case where we need to quote and but don't recognize the
|
||||
// tag, throw.
|
||||
// Identifiers with lower case or hypthens are fallback tags (strings).
|
||||
// XJSMemberExpressions are not.
|
||||
if (nameObject.type === Syntax.XJSIdentifier && isTagName(nameObject.name)) {
|
||||
// This is a temporary error message to assist upgrades
|
||||
if (!FALLBACK_TAGS.hasOwnProperty(nameObject.name)) {
|
||||
throw new Error(
|
||||
'Tags must be valid JS identifiers or a recognized special case. `<' +
|
||||
tagName + '>` is not one of them.'
|
||||
'Lower case component names (' + nameObject.name + ') are no longer ' +
|
||||
'supported in JSX: See http://fb.me/react-jsx-lower-case'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Use utils.catchup in this case so we can easily handle XJSMemberExpressions
|
||||
// which look like Foo.Bar.Baz. This also handles unhyphenated XJSIdentifiers
|
||||
// that aren't fallback tags.
|
||||
if (!didAddTag) {
|
||||
utils.append('"' + nameObject.name + '"', state);
|
||||
utils.move(nameObject.range[1], state);
|
||||
} else {
|
||||
// Use utils.catchup in this case so we can easily handle
|
||||
// XJSMemberExpressions which look like Foo.Bar.Baz. This also handles
|
||||
// XJSIdentifiers that aren't fallback tags.
|
||||
utils.move(nameObject.range[0], state);
|
||||
utils.catchup(nameObject.range[1], state);
|
||||
}
|
||||
|
||||
if (isReact) {
|
||||
utils.append(', ', state);
|
||||
} else {
|
||||
utils.append('(', state);
|
||||
}
|
||||
utils.append(', ', state);
|
||||
|
||||
var hasAttributes = attributesObject.length;
|
||||
|
||||
|
@ -147,7 +116,6 @@ function visitReactTag(traverse, object, path, state) {
|
|||
utils.append('}, ', state);
|
||||
}
|
||||
|
||||
|
||||
// Move to the expression start, ignoring everything except parenthesis
|
||||
// and whitespace.
|
||||
utils.catchup(attr.range[0], state, stripNonWhiteParen);
|
||||
|
@ -203,13 +171,7 @@ function visitReactTag(traverse, object, path, state) {
|
|||
utils.move(attr.name.range[1], state);
|
||||
// Use catchupNewlines to skip over the '=' in the attribute
|
||||
utils.catchupNewlines(attr.value.range[0], state);
|
||||
if (JSX_ATTRIBUTE_TRANSFORMS.hasOwnProperty(attr.name.name)) {
|
||||
utils.append(JSX_ATTRIBUTE_TRANSFORMS[attr.name.name](attr), state);
|
||||
utils.move(attr.value.range[1], state);
|
||||
if (!isLast) {
|
||||
utils.append(', ', state);
|
||||
}
|
||||
} else if (attr.value.type === Syntax.Literal) {
|
||||
if (attr.value.type === Syntax.Literal) {
|
||||
renderXJSLiteral(attr.value, isLast, state);
|
||||
} else {
|
||||
renderXJSExpressionContainer(traverse, attr.value, isLast, path, state);
|
||||
|
|
Loading…
Reference in New Issue