diff --git a/src/core/__tests__/jsxns-test.js b/src/core/__tests__/jsxns-test.js
new file mode 100644
index 0000000000000..c362ddf33fb03
--- /dev/null
+++ b/src/core/__tests__/jsxns-test.js
@@ -0,0 +1,89 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @jsx React.DOM
+ * @jsxns {"MyNamespace":"SampleNamespace"}
+ * @emails react-core
+ */
+
+"use strict";
+
+var React = require('React');
+var ReactTestUtils = require('ReactTestUtils');
+
+var reactComponentExpect= require('reactComponentExpect');
+
+var SampleNamespace = {
+ ComponentA: React.createClass({
+ render: function() {
+ return (
+
+ Test
+
+ );
+ }
+ }),
+
+ ComponentB: React.createClass({
+ render: function() {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+ })
+};
+
+var renderJSXNSComponent = function() {
+ var testJSXNSComponent =
+ ReactTestUtils.renderIntoDocument();
+
+ reactComponentExpect(testJSXNSComponent)
+ .toBeCompositeComponentWithType(SampleNamespace.ComponentA);
+
+ var generalContainer = testJSXNSComponent.refs.childComponent;
+
+ reactComponentExpect(generalContainer)
+ .toBeCompositeComponentWithType(SampleNamespace.ComponentB);
+
+ return testJSXNSComponent;
+};
+
+describe('jsxns', function() {
+ beforeEach(function() {
+ require('mock-modules').dumpCache();
+ });
+
+ /**
+ * Ensure that component respects namespace correctly.
+ */
+ it("Should render namespaced components correctly", function() {
+ var testJSXNSComponent = renderJSXNSComponent();
+
+ var child = ReactTestUtils.findRenderedDOMComponentWithClass(testJSXNSComponent, 'wrapper');
+
+ expect(child).toBeDefined();
+ });
+
+ /**
+ * Fails for undefined namespaces.
+ */
+ it("Should fail for undefined namespace", function() {
+ expect(function() {
+ ReactTestUtils.renderIntoDocument();
+ }).toThrow();
+ });
+});
diff --git a/vendor/fbtransform/transforms/react.js b/vendor/fbtransform/transforms/react.js
index 2e50fb9b33108..85850016eac95 100644
--- a/vendor/fbtransform/transforms/react.js
+++ b/vendor/fbtransform/transforms/react.js
@@ -47,22 +47,54 @@ var JSX_ATTRIBUTE_TRANSFORMS = {
}
};
-function visitReactTag(traverse, object, path, state) {
+function loadJSXNamespaces(state) {
+ var namespaces = {};
+ var jsxns = utils.getDocblock(state).jsxns;
+
+ if (jsxns) {
+ try {
+ namespaces = JSON.parse(jsxns);
+ } catch (e) {
+ throw new SyntaxError('Error parsing @jsxns docblock: ' + e.message);
+ }
+ }
+
+ return namespaces;
+}
+
+function normalizeTag(nameObject, state) {
var jsxObjIdent = utils.getDocblock(state).jsx;
+ var jsxNamespaces = loadJSXNamespaces(state);
+ var isFallbackTag = FALLBACK_TAGS.hasOwnProperty(nameObject.name);
+ var namespace = nameObject.namespace;
+
+ if (isFallbackTag) {
+ namespace = jsxObjIdent;
+ } else if (jsxNamespaces.hasOwnProperty(namespace)) {
+ namespace = jsxNamespaces[namespace];
+ } else if (namespace) {
+ throw new Error(
+ 'Undefined namespace used on tag: ' + namespace + ':' + nameObject.name
+ + '. Define any custom namespaces with @jsxns.');
+ }
+
+ if (namespace) {
+ return namespace + '.' + nameObject.name;
+ } else {
+ return nameObject.name;
+ }
+}
+
+function visitReactTag(traverse, object, path, state) {
var openingElement = object.openingElement;
var nameObject = openingElement.name;
var attributesObject = openingElement.attributes;
+ var tagIdentifier = normalizeTag(nameObject, state);
utils.catchup(openingElement.range[0], state);
- if (nameObject.namespace) {
- throw new Error(
- 'Namespace tags are not supported. ReactJSX is not XML.');
- }
-
- var isFallbackTag = FALLBACK_TAGS.hasOwnProperty(nameObject.name);
utils.append(
- (isFallbackTag ? jsxObjIdent + '.' : '') + (nameObject.name) + '(',
+ tagIdentifier + '(',
state
);