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 ', function() {
259
var code = [
296
- '/**',
297
- ' * @jsx React.DOM',
298
- ' */',
260
'<div> </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 even coupled with other whitespace', function() {
270
var code = [
313
- '/**',
314
- ' * @jsx React.DOM',
315
- ' */',
271
'<div> </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);