@samitouri / QOS-React-2 / commits / c4658c1728

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

Sebastian Markbage committed Oct 7, 2014 at 10:44 UTC c4658c1728b39c452a86f371ecb1c51874456107
2 files changed +61 -172
vendor/fbtransform/transforms/__tests__/react-test.js
+33 -106
@@ -13,7 +13,7 @@
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 - * @emails jeffmo@fb.com
16 + * @emails react-core
17 */
18
19 /*jshint evil:true, unused:false*/
@@ -59,34 +59,21 @@ describe('react jsx', function() {
59 };
60
61 it('should convert simple tags', function() {
62 - var code = [
63 - '/**@jsx React.DOM*/',
64 - 'var x = <div></div>;'
65 - ].join('\n');
66 - var result = [
67 - '/**@jsx React.DOM*/',
68 - 'var x = React.createElement(React.DOM.div, null);'
69 - ].join('\n');
62 + var code = 'var x = <div></div>;';
63 + var result = 'var x = React.createElement("div", null);';
64
65 expect(transform(code).code).toEqual(result);
66 });
67
68 it('should convert simple text', function() {
75 - var code = [
76 - '/**@jsx React.DOM*/\n' +
77 - 'var x = <div>text</div>;'
78 - ].join('\n');
79 - var result = [
80 - '/**@jsx React.DOM*/',
81 - 'var x = React.createElement(React.DOM.div, null, "text");'
82 - ].join('\n');
69 + var code = 'var x = <div>text</div>;';
70 + var result = 'var x = React.createElement("div", null, "text");';
71
72 expect(transform(code).code).toEqual(result);
73 });
74
75 it('should have correct comma in nested children', function() {
76 var code = [
89 - '/**@jsx React.DOM*/',
77 'var x = <div>',
78 ' <div><br /></div>',
79 ' <Component>{foo}<br />{bar}</Component>',
@@ -94,13 +81,12 @@ describe('react jsx', function() {
81 '</div>;'
82 ].join('\n');
83 var result = [
97 - '/**@jsx React.DOM*/',
98 - 'var x = React.createElement(React.DOM.div, null, ',
99 - ' React.createElement(React.DOM.div, null, ' +
100 - 'React.createElement(React.DOM.br, null)), ',
84 + 'var x = React.createElement("div", null, ',
85 + ' React.createElement("div", null, ' +
86 + 'React.createElement("br", null)), ',
87 ' React.createElement(Component, null, foo, ' +
102 - 'React.createElement(React.DOM.br, null), bar), ',
103 - ' React.createElement(React.DOM.br, null)',
88 + 'React.createElement("br", null), bar), ',
89 + ' React.createElement("br", null)',
90 ');'
91 ].join('\n');
92
@@ -110,14 +96,12 @@ describe('react jsx', function() {
96 it('should avoid wrapping in extra parens if not needed', function() {
97 // Try with a single composite child, wrapped in a div.
98 var code = [
113 - '/**@jsx React.DOM*/',
99 'var x = <div>',
100 ' <Component />',
101 '</div>;'
102 ].join('\n');
103 var result = [
119 - '/**@jsx React.DOM*/',
120 - 'var x = React.createElement(React.DOM.div, null, ',
104 + 'var x = React.createElement("div", null, ',
105 ' React.createElement(Component, null)',
106 ');'
107 ].join('\n');
@@ -126,14 +110,12 @@ describe('react jsx', function() {
110
111 // Try with a single interpolated child, wrapped in a div.
112 code = [
129 - '/**@jsx React.DOM*/',
113 'var x = <div>',
114 ' {this.props.children}',
115 '</div>;'
116 ].join('\n');
117 result = [
135 - '/**@jsx React.DOM*/',
136 - 'var x = React.createElement(React.DOM.div, null, ',
118 + 'var x = React.createElement("div", null, ',
119 ' this.props.children',
120 ');'
121 ].join('\n');
@@ -141,13 +123,11 @@ describe('react jsx', function() {
123
124 // Try with a single interpolated child, wrapped in a composite.
125 code = [
144 - '/**@jsx React.DOM*/',
126 'var x = <Composite>',
127 ' {this.props.children}',
128 '</Composite>;'
129 ].join('\n');
130 result = [
150 - '/**@jsx React.DOM*/',
131 'var x = React.createElement(Composite, null, ',
132 ' this.props.children',
133 ');'
@@ -156,13 +136,11 @@ describe('react jsx', function() {
136
137 // Try with a single composite child, wrapped in a composite.
138 code = [
159 - '/**@jsx React.DOM*/',
139 'var x = <Composite>',
140 ' <Composite2 />',
141 '</Composite>;'
142 ].join('\n');
143 result = [
165 - '/**@jsx React.DOM*/',
144 'var x = React.createElement(Composite, null, ',
145 ' React.createElement(Composite2, null)',
146 ');'
@@ -172,7 +150,6 @@ describe('react jsx', function() {
150
151 it('should insert commas after expressions before whitespace', function() {
152 var code = [
175 - '/**@jsx React.DOM*/',
153 'var x =',
154 ' <div',
155 ' attr1={',
@@ -192,9 +169,8 @@ describe('react jsx', function() {
169 ' </div>;'
170 ].join('\n');
171 var result = [
195 - '/**@jsx React.DOM*/',
172 'var x =',
197 - ' React.createElement(React.DOM.div, {',
173 + ' React.createElement("div", {',
174 ' attr1: ',
175 ' "foo" + "bar", ',
176 ' ',
@@ -217,9 +193,6 @@ describe('react jsx', function() {
193
194 it('should properly handle comments adjacent to children', function() {
195 var code = [
220 - '/**',
221 - ' * @jsx React.DOM',
222 - ' */',
196 'var x = (',
197 ' <div>',
198 ' {/* A comment at the beginning */}',
@@ -235,18 +208,15 @@ describe('react jsx', function() {
208 ');'
209 ].join('\n');
210 var result = [
238 - '/**',
239 - ' * @jsx React.DOM',
240 - ' */',
211 'var x = (',
242 - ' React.createElement(React.DOM.div, null, ',
212 + ' React.createElement("div", null, ',
213 ' /* A comment at the beginning */',
214 ' /* A second comment at the beginning */',
245 - ' React.createElement(React.DOM.span, null',
215 + ' React.createElement("span", null',
216 ' /* A nested comment */',
217 ' ), ',
218 ' /* A sandwiched comment */',
249 - ' React.createElement(React.DOM.br, null)',
219 + ' React.createElement("br", null)',
220 ' /* A comment at the end */',
221 ' /* A second comment at the end */',
222 ' )',
@@ -258,9 +228,6 @@ describe('react jsx', function() {
228
229 it('should properly handle comments between props', function() {
230 var code = [
261 - '/**',
262 - ' * @jsx React.DOM',
263 - ' */',
231 'var x = (',
232 ' <div',
233 ' /* a multi-line',
@@ -273,15 +240,12 @@ describe('react jsx', function() {
240 ');'
241 ].join('\n');
242 var result = [
276 - '/**',
277 - ' * @jsx React.DOM',
278 - ' */',
243 'var x = (',
280 - ' React.createElement(React.DOM.div, {',
244 + ' React.createElement("div", {',
245 ' /* a multi-line',
246 ' comment */',
247 ' attr1: "foo"}, ',
284 - ' React.createElement(React.DOM.span, {// a double-slash comment',
248 + ' React.createElement("span", {// a double-slash comment',
249 ' attr2: "bar"}',
250 ' )',
251 ' )',
@@ -293,16 +257,10 @@ describe('react jsx', function() {
257
258 it('should not strip tags with a single child of &nbsp;', function() {
259 var code = [
296 - '/**',
297 - ' * @jsx React.DOM',
298 - ' */',
260 '<div>&nbsp;</div>;'
261 ].join('\n');
262 var result = [
302 - '/**',
303 - ' * @jsx React.DOM',
304 - ' */',
305 - 'React.createElement(React.DOM.div, null, "\u00A0");'
263 + 'React.createElement("div", null, "\u00A0");'
264 ].join('\n');
265
266 expect(transform(code).code).toBe(result);
@@ -310,16 +268,10 @@ describe('react jsx', function() {
268
269 it('should not strip &nbsp; even coupled with other whitespace', function() {
270 var code = [
313 - '/**',
314 - ' * @jsx React.DOM',
315 - ' */',
271 '<div>&nbsp; </div>;'
272 ].join('\n');
273 var result = [
319 - '/**',
320 - ' * @jsx React.DOM',
321 - ' */',
322 - 'React.createElement(React.DOM.div, null, "\u00A0 ");'
274 + 'React.createElement("div", null, "\u00A0 ");'
275 ].join('\n');
276
277 expect(transform(code).code).toBe(result);
@@ -327,9 +279,13 @@ describe('react jsx', function() {
279
280 it('should handle hasOwnProperty correctly', function() {
281 var code = '<hasOwnProperty>testing</hasOwnProperty>;';
330 - var result = 'React.createElement(hasOwnProperty, null, "testing");';
282 + // var result = 'React.createElement("hasOwnProperty", null, "testing");';
283
332 - expect(transform(code).code).toBe(result);
284 + // expect(transform(code).code).toBe(result);
285 +
286 + // This is currently not supported, and will generate a string tag in
287 + // a follow up.
288 + expect(() => transform(code)).toThrow();
289 });
290
291 it('should allow constructor as prop', function() {
@@ -347,29 +303,15 @@ describe('react jsx', function() {
303 });
304
305 it('should allow deeper JS namespacing', function() {
350 - var code = [
351 - '/**',
352 - ' * @jsx React.DOM',
353 - ' */',
354 - '<Namespace.DeepNamespace.Component />;'
355 - ].join('\n');
356 - var result = [
357 - '/**',
358 - ' * @jsx React.DOM',
359 - ' */',
360 - 'React.createElement(Namespace.DeepNamespace.Component, null);'
361 - ].join('\n');
306 + var code = '<Namespace.DeepNamespace.Component />;';
307 + var result =
308 + 'React.createElement(Namespace.DeepNamespace.Component, null);';
309
310 expect(transform(code).code).toBe(result);
311 });
312
313 it('should disallow XML namespacing', function() {
367 - var code = [
368 - '/**',
369 - ' * @jsx React.DOM',
370 - ' */',
371 - '<Namespace:Component />;'
372 - ].join('\n');
314 + var code = '<Namespace:Component />;';
315
316 expect(() => transform(code)).toThrow();
317 });
@@ -399,18 +341,8 @@ describe('react jsx', function() {
341 });
342
343 it('should transform known hyphenated tags', function() {
402 - var code = [
403 - '/**',
404 - ' * @jsx React.DOM',
405 - ' */',
406 - '<font-face />;'
407 - ].join('\n');
408 - var result = [
409 - '/**',
410 - ' * @jsx React.DOM',
411 - ' */',
412 - 'React.createElement(React.DOM[\'font-face\'], null);'
413 - ].join('\n');
344 + var code = '<font-face />;';
345 + var result = 'React.createElement("font-face", null);';
346
347 expect(transform(code).code).toBe(result);
348 });
@@ -422,12 +354,7 @@ describe('react jsx', function() {
354 });
355
356 it('should throw for unknown hyphenated tags', function() {
425 - var code = [
426 - '/**',
427 - ' * @jsx React.DOM',
428 - ' */',
429 - '<x-component />;'
430 - ].join('\n');
357 + var code = '<x-component />;';
358
359 expect(() => transform(code)).toThrow();
360 });
vendor/fbtransform/transforms/react.js
+28 -66
@@ -28,27 +28,16 @@ var quoteAttrName = require('./xjs').quoteAttrName;
28 var trimLeft = require('./xjs').trimLeft;
29
30 /**
31 - * Customized desugar processor.
31 + * Customized desugar processor for React JSX. Currently:
32 *
33 - * Currently: (Somewhat tailored to React)
34 - * <X> </X> => X(null, null)
35 - * <X prop="1" /> => X({prop: '1'}, null)
36 - * <X prop="2"><Y /></X> => X({prop:'2'}, Y(null, null))
37 - * <X prop="2"><Y /><Z /></X> => X({prop:'2'}, [Y(null, null), Z(null, null)])
38 - *
39 - * Exceptions to the simple rules above:
40 - * if a property is named "class" it will be changed to "className" in the
41 - * javascript since "class" is not a valid object key in javascript.
33 + * <X> </X> => React.createElement(X, null)
34 + * <X prop="1" /> => React.createElement(X, {prop: '1'}, null)
35 + * <X prop="2"><Y /></X> => React.createElement(X, {prop:'2'},
36 + * React.createElement(Y, null)
37 + * )
38 + * <div /> => React.createElement("div", null)
39 */
40
44 -var JSX_ATTRIBUTE_TRANSFORMS = {
45 - cxName: function(attr) {
46 - throw new Error(
47 - "cxName is no longer supported, use className={cx(...)} instead"
48 - );
49 - }
50 -};
51 -
41 /**
42 * Removes all non-whitespace/parenthesis characters
43 */
@@ -57,8 +46,12 @@ function stripNonWhiteParen(value) {
46 return value.replace(reNonWhiteParen, '');
47 }
48
49 +var tagConvention = /^[a-z]|\-/;
50 +function isTagName(name) {
51 + return tagConvention.test(name);
52 +}
53 +
54 function visitReactTag(traverse, object, path, state) {
61 - var jsxObjIdent = utils.getDocblock(state).jsx;
55 var openingElement = object.openingElement;
56 var nameObject = openingElement.name;
57 var attributesObject = openingElement.attributes;
@@ -69,55 +62,31 @@ function visitReactTag(traverse, object, path, state) {
62 throw new Error('Namespace tags are not supported. ReactJSX is not XML.');
63 }
64
72 - var isReact = jsxObjIdent !== 'JSXDOM';
73 -
65 // We assume that the React runtime is already in scope
75 - if (isReact) {
76 - utils.append('React.createElement(', state);
77 - }
66 + utils.append('React.createElement(', state);
67
79 - // Only identifiers can be fallback tags or need quoting. We don't need to
80 - // handle quoting for other types.
81 - var didAddTag = false;
82 -
83 - // Only identifiers can be fallback tags. XJSMemberExpressions are not.
84 - if (nameObject.type === Syntax.XJSIdentifier) {
85 - var tagName = nameObject.name;
86 - var quotedTagName = quoteAttrName(tagName);
87 -
88 - if (FALLBACK_TAGS.hasOwnProperty(tagName)) {
89 - // "Properly" handle invalid identifiers, like <font-face>, which needs to
90 - // be enclosed in quotes.
91 - var predicate =
92 - tagName === quotedTagName ?
93 - ('.' + tagName) :
94 - ('[' + quotedTagName + ']');
95 - utils.append(jsxObjIdent + predicate, state);
96 - utils.move(nameObject.range[1], state);
97 - didAddTag = true;
98 - } else if (tagName !== quotedTagName) {
99 - // If we're in the case where we need to quote and but don't recognize the
100 - // tag, throw.
68 + // Identifiers with lower case or hypthens are fallback tags (strings).
69 + // XJSMemberExpressions are not.
70 + if (nameObject.type === Syntax.XJSIdentifier && isTagName(nameObject.name)) {
71 + // This is a temporary error message to assist upgrades
72 + if (!FALLBACK_TAGS.hasOwnProperty(nameObject.name)) {
73 throw new Error(
102 - 'Tags must be valid JS identifiers or a recognized special case. `<' +
103 - tagName + '>` is not one of them.'
74 + 'Lower case component names (' + nameObject.name + ') are no longer ' +
75 + 'supported in JSX: See http://fb.me/react-jsx-lower-case'
76 );
77 }
106 - }
78
108 - // Use utils.catchup in this case so we can easily handle XJSMemberExpressions
109 - // which look like Foo.Bar.Baz. This also handles unhyphenated XJSIdentifiers
110 - // that aren't fallback tags.
111 - if (!didAddTag) {
79 + utils.append('"' + nameObject.name + '"', state);
80 + utils.move(nameObject.range[1], state);
81 + } else {
82 + // Use utils.catchup in this case so we can easily handle
83 + // XJSMemberExpressions which look like Foo.Bar.Baz. This also handles
84 + // XJSIdentifiers that aren't fallback tags.
85 utils.move(nameObject.range[0], state);
86 utils.catchup(nameObject.range[1], state);
87 }
88
116 - if (isReact) {
117 - utils.append(', ', state);
118 - } else {
119 - utils.append('(', state);
120 - }
89 + utils.append(', ', state);
90
91 var hasAttributes = attributesObject.length;
92
@@ -147,7 +116,6 @@ function visitReactTag(traverse, object, path, state) {
116 utils.append('}, ', state);
117 }
118
150 -
119 // Move to the expression start, ignoring everything except parenthesis
120 // and whitespace.
121 utils.catchup(attr.range[0], state, stripNonWhiteParen);
@@ -203,13 +171,7 @@ function visitReactTag(traverse, object, path, state) {
171 utils.move(attr.name.range[1], state);
172 // Use catchupNewlines to skip over the '=' in the attribute
173 utils.catchupNewlines(attr.value.range[0], state);
206 - if (JSX_ATTRIBUTE_TRANSFORMS.hasOwnProperty(attr.name.name)) {
207 - utils.append(JSX_ATTRIBUTE_TRANSFORMS[attr.name.name](attr), state);
208 - utils.move(attr.value.range[1], state);
209 - if (!isLast) {
210 - utils.append(', ', state);
211 - }
212 - } else if (attr.value.type === Syntax.Literal) {
174 + if (attr.value.type === Syntax.Literal) {
175 renderXJSLiteral(attr.value, isLast, state);
176 } else {
177 renderXJSExpressionContainer(traverse, attr.value, isLast, path, state);