Add support to JSX transform for <hyphenated-tags>
This only adds support for whitelisted tags, not arbitrary ones. Closes #1539
Paul O’Shannessy committed
Jun 30, 2014 at 20:47 UTC
5eb57fa51b4702e76e8c71535b695a216b803eb2
2 files changed
+87
-9
vendor/fbtransform/transforms/__tests__/react-test.js
+53
-1
@@ -13,7 +13,7 @@
13
* See the License for the specific language governing permissions and
14
* limitations under the License.
15
*
16
- * @emails javascript@lists.facebook.com
16
+ * @emails jeffmo@fb.com
17
*/
18
"use strict";
19
@@ -21,6 +21,12 @@ require('mock-modules').autoMockOff();
21
22
describe('react jsx', function() {
23
var transformAll = require('../../syntax.js').transformAll;
24
+ var xjs = require('../xjs.js');
25
+
26
+ // Add <font-face> to list of known tags to ensure that when we test support
27
+ // for hyphentated known tags, it's there.
28
+ // TODO: remove this when/if <font-face> is supported out of the box.
29
+ xjs.knownTags['font-face'] = true;
30
31
var transform = function(code, options, excludes) {
32
return transformAll(
@@ -366,6 +372,23 @@ describe('react jsx', function() {
372
expect(transform(code).code).toBe(result);
373
});
374
375
+ it('should allow deeper JS namespacing', function() {
376
+ var code = [
377
+ '/**',
378
+ ' * @jsx React.DOM',
379
+ ' */',
380
+ '<Namespace.DeepNamespace.Component />;'
381
+ ].join('\n');
382
+ var result = [
383
+ '/**',
384
+ ' * @jsx React.DOM',
385
+ ' */',
386
+ 'Namespace.DeepNamespace.Component(null);'
387
+ ].join('\n');
388
+
389
+ expect(transform(code).code).toBe(result);
390
+ });
391
+
392
it('should disallow XML namespacing', function() {
393
var code = [
394
'/**',
@@ -382,6 +405,24 @@ describe('react jsx', function() {
405
'<Component { ... x } y\n={2 } z />';
406
var result = HEADER +
407
'Component(Object.assign({}, x , {y: \n2, z: true}))';
408
+
409
+ expect(transform(code).code).toBe(result);
410
+ });
411
+
412
+ it('should transform known hyphenated tags', function() {
413
+ var code = [
414
+ '/**',
415
+ ' * @jsx React.DOM',
416
+ ' */',
417
+ '<font-face />;'
418
+ ].join('\n');
419
+ var result = [
420
+ '/**',
421
+ ' * @jsx React.DOM',
422
+ ' */',
423
+ 'React.DOM[\'font-face\'](null);'
424
+ ].join('\n');
425
+
426
expect(transform(code).code).toBe(result);
427
});
428
@@ -391,6 +432,17 @@ describe('react jsx', function() {
432
).not.toBeCalled();
433
});
434
435
+ it('should throw for unknown hyphenated tags', function() {
436
+ var code = [
437
+ '/**',
438
+ ' * @jsx React.DOM',
439
+ ' */',
440
+ '<x-component />;'
441
+ ].join('\n');
442
+
443
+ expect(() => transform(code)).toThrow();
444
+ });
445
+
446
it('calls assign with a new target object for spreads', function() {
447
expectObjectAssign(
448
'<Component {...x} />'
vendor/fbtransform/transforms/react.js
+34
-8
@@ -65,20 +65,46 @@ function visitReactTag(traverse, object, path, state) {
65
66
utils.catchup(openingElement.range[0], state, trimLeft);
67
68
-
68
if (nameObject.type === Syntax.XJSNamespacedName && nameObject.namespace) {
69
throw new Error('Namespace tags are not supported. ReactJSX is not XML.');
70
}
71
73
- // Only identifiers can be fallback tags. XJSMemberExpressions are not.
74
- var isFallbackTag =
75
- nameObject.type === Syntax.XJSIdentifier &&
76
- FALLBACK_TAGS.hasOwnProperty(nameObject.name);
72
+ // Only identifiers can be fallback tags or need quoting. We don't need to
73
+ // handle quoting for other types.
74
+ var didAddTag = false;
75
78
- utils.append(isFallbackTag ? jsxObjIdent + '.' : '', state);
76
+ // Only identifiers can be fallback tags. XJSMemberExpressions are not.
77
+ if (nameObject.type === Syntax.XJSIdentifier) {
78
+ var tagName = nameObject.name;
79
+ var quotedTagName = quoteAttrName(tagName);
80
+
81
+ if (FALLBACK_TAGS.hasOwnProperty(tagName)) {
82
+ // "Properly" handle invalid identifiers, like <font-face>, which needs to
83
+ // be enclosed in quotes.
84
+ var predicate =
85
+ tagName === quotedTagName ?
86
+ ('.' + tagName) :
87
+ ('[' + quotedTagName + ']');
88
+ utils.append(jsxObjIdent + predicate, state);
89
+ utils.move(nameObject.range[1], state);
90
+ didAddTag = true;
91
+ } else if (tagName !== quotedTagName) {
92
+ // If we're in the case where we need to quote and but don't recognize the
93
+ // tag, throw.
94
+ throw new Error(
95
+ 'Tags must be valid JS identifiers or a recognized special case. `<' +
96
+ tagName + '>` is not one of them.'
97
+ );
98
+ }
99
+ }
100
80
- utils.move(nameObject.range[0], state);
81
- utils.catchup(nameObject.range[1], state);
101
+ // Use utils.catchup in this case so we can easily handle XJSMemberExpressions
102
+ // which look like Foo.Bar.Baz. This also handles unhyphenated XJSIdentifiers
103
+ // that aren't fallback tags.
104
+ if (!didAddTag) {
105
+ utils.move(nameObject.range[0], state);
106
+ utils.catchup(nameObject.range[1], state);
107
+ }
108
109
utils.append('(', state);
110