Skip to content

Commit 5eb57fa

Browse files
committed
Add support to JSX transform for <hyphenated-tags>
This only adds support for whitelisted tags, not arbitrary ones. Closes #1539
1 parent 3e5c606 commit 5eb57fa

File tree

2 files changed

+87
-9
lines changed

2 files changed

+87
-9
lines changed

vendor/fbtransform/transforms/__tests__/react-test.js

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,20 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*
16-
* @emails [email protected].com
16+
* @emails jeffmo@fb.com
1717
*/
1818
"use strict";
1919

2020
require('mock-modules').autoMockOff();
2121

2222
describe('react jsx', function() {
2323
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;
2430

2531
var transform = function(code, options, excludes) {
2632
return transformAll(
@@ -366,6 +372,23 @@ describe('react jsx', function() {
366372
expect(transform(code).code).toBe(result);
367373
});
368374

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+
369392
it('should disallow XML namespacing', function() {
370393
var code = [
371394
'/**',
@@ -382,6 +405,24 @@ describe('react jsx', function() {
382405
'<Component { ... x } y\n={2 } z />';
383406
var result = HEADER +
384407
'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+
385426
expect(transform(code).code).toBe(result);
386427
});
387428

@@ -391,6 +432,17 @@ describe('react jsx', function() {
391432
).not.toBeCalled();
392433
});
393434

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+
394446
it('calls assign with a new target object for spreads', function() {
395447
expectObjectAssign(
396448
'<Component {...x} />'

vendor/fbtransform/transforms/react.js

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,20 +65,46 @@ function visitReactTag(traverse, object, path, state) {
6565

6666
utils.catchup(openingElement.range[0], state, trimLeft);
6767

68-
6968
if (nameObject.type === Syntax.XJSNamespacedName && nameObject.namespace) {
7069
throw new Error('Namespace tags are not supported. ReactJSX is not XML.');
7170
}
7271

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;
7775

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+
}
79100

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+
}
82108

83109
utils.append('(', state);
84110

0 commit comments

Comments
 (0)