diff --git a/lib/6to5/transform.js b/lib/6to5/transform.js
index 1f3a3e96f3..25bc7d8003 100644
--- a/lib/6to5/transform.js
+++ b/lib/6to5/transform.js
@@ -96,6 +96,9 @@ transform.transformers = {
unicodeRegex: require("./transformers/unicode-regex"),
generators: require("./transformers/generators"),
+ react: require("./transformers/react"),
+ jsx: require("./transformers/jsx"),
+
_aliasFunctions: require("./transformers/_alias-functions"),
_blockHoist: require("./transformers/_block-hoist"),
_declarations: require("./transformers/_declarations"),
diff --git a/lib/6to5/transformers/jsx/index.js b/lib/6to5/transformers/jsx/index.js
new file mode 100644
index 0000000000..f3e6477421
--- /dev/null
+++ b/lib/6to5/transformers/jsx/index.js
@@ -0,0 +1,99 @@
+// Based upon the excellent jsx-transpiler by Ingvar Stepanyan (RReverser)
+// https://github.com/RReverser/jsx-transpiler
+
+var esutils = require("esutils");
+var b = require("recast").types.builders;
+var _ = require("lodash");
+
+var JSX_ANNOTATION_REGEX = /^\*\s*@jsx\s+([^\s]+)/;
+var KNOWN_TAGS = require("./known-tags");
+
+exports.Program = function (node, parent, file) {
+ var jsx = "React.DOM";
+
+ // looking for namespace annotation
+ _.each(node.comments, function (comment) {
+ if (!comment.possiblyLeading) return;
+
+ var matches = JSX_ANNOTATION_REGEX.exec(comment.value);
+ if (matches) jsx = matches[1];
+ });
+
+ // prebuilding AST node
+ file.jsx = jsx.split(".").map(b.identifier).reduce(function (object, property) {
+ return b.memberExpression(object, property, false);
+ });
+};
+
+exports.XJSIdentifier = function (node) {
+ if (esutils.keyword.isIdentifierName(node.name)) {
+ node.type = "Identifier";
+ } else {
+ return b.literal(node.name);
+ }
+};
+
+exports.XJSNamespacedName = function () {
+ throw new Error("Namespace tags are not supported. ReactJSX is not XML.");
+};
+
+exports.XJSMemberExpression = {
+ exit: function (node) {
+ node.computed = node.property.type === "Literal";
+ node.type = "MemberExpression";
+ }
+};
+
+exports.XJSEmptyExpression = function (node) {
+ node.value = null;
+ node.type = "Literal";
+};
+
+exports.XJSExpressionContainer = function (node) {
+ return node.expression;
+};
+
+exports.XJSAttribute = {
+ exit: function (node) {
+ var value = node.value || b.literal(true);
+ var propNode = b.property("init", node.name, value);
+ propNode.loc = node.loc;
+ return propNode;
+ }
+};
+
+exports.XJSOpeningElement = {
+ exit: function (node, parent, file) {
+ var tagExpr = node.name;
+
+ if (_.contains(KNOWN_TAGS, tagExpr.name)) {
+ tagExpr = b.memberExpression(file.jsx, tagExpr, false);
+ }
+
+ var props = node.attributes;
+ if (props.length) {
+ props = b.objectExpression(props);
+ } else {
+ props = b.literal(null);
+ }
+
+ return b.callExpression(tagExpr, [props]);
+ }
+};
+
+exports.XJSElement = {
+ exit: function (node) {
+ var callExpr = node.openingElement;
+ var children = node.children;
+ var args = callExpr.arguments;
+
+ switch (children.length) {
+ case 0: break;
+ case 1: args.push(children[0]); break;
+ default: args.push(b.arrayExpression(children));
+ }
+
+ callExpr.loc = node.loc;
+ return callExpr;
+ }
+};
diff --git a/lib/6to5/transformers/jsx/known-tags.json b/lib/6to5/transformers/jsx/known-tags.json
new file mode 100644
index 0000000000..5a530c5bb9
--- /dev/null
+++ b/lib/6to5/transformers/jsx/known-tags.json
@@ -0,0 +1,132 @@
+[
+ "a",
+ "abbr",
+ "address",
+ "applet",
+ "area",
+ "article",
+ "aside",
+ "audio",
+ "b",
+ "base",
+ "bdi",
+ "bdo",
+ "big",
+ "blockquote",
+ "body",
+ "br",
+ "button",
+ "canvas",
+ "caption",
+ "circle",
+ "cite",
+ "code",
+ "col",
+ "colgroup",
+ "command",
+ "data",
+ "datalist",
+ "dd",
+ "defs",
+ "del",
+ "details",
+ "dfn",
+ "dialog",
+ "div",
+ "dl",
+ "dt",
+ "ellipse",
+ "em",
+ "embed",
+ "fieldset",
+ "figcaption",
+ "figure",
+ "footer",
+ "form",
+ "g",
+ "h1",
+ "h2",
+ "h3",
+ "h4",
+ "h5",
+ "h6",
+ "head",
+ "header",
+ "hgroup",
+ "hr",
+ "html",
+ "i",
+ "iframe",
+ "img",
+ "input",
+ "ins",
+ "kbd",
+ "keygen",
+ "label",
+ "legend",
+ "li",
+ "line",
+ "linearGradient",
+ "link",
+ "main",
+ "map",
+ "mark",
+ "marquee",
+ "menu",
+ "menuitem",
+ "meta",
+ "meter",
+ "nav",
+ "noscript",
+ "object",
+ "ol",
+ "optgroup",
+ "option",
+ "output",
+ "p",
+ "param",
+ "path",
+ "polygon",
+ "polyline",
+ "pre",
+ "progress",
+ "q",
+ "radialGradient",
+ "rect",
+ "rp",
+ "rt",
+ "ruby",
+ "s",
+ "samp",
+ "script",
+ "section",
+ "select",
+ "small",
+ "source",
+ "span",
+ "stop",
+ "strong",
+ "style",
+ "sub",
+ "summary",
+ "sup",
+ "svg",
+ "table",
+ "tbody",
+ "td",
+ "text",
+ "textarea",
+ "tfoot",
+ "th",
+ "thead",
+ "time",
+ "title",
+ "tr",
+ "track",
+ "tspan",
+ "u",
+ "ul",
+ "var",
+ "video",
+ "wbr"
+]
diff --git a/lib/6to5/transformers/react.js b/lib/6to5/transformers/react.js
new file mode 100644
index 0000000000..9a413abba7
--- /dev/null
+++ b/lib/6to5/transformers/react.js
@@ -0,0 +1,63 @@
+var b = require("recast").types.builders;
+var _ = require("lodash");
+
+var addDisplayName = function (id, call) {
+ if (!call || call.type !== "CallExpression") return;
+
+ var callee = call.callee;
+ if (callee.type !== "MemberExpression") return;
+
+ // not React
+ var obj = callee.object;
+ if (obj.type !== "Identifier" || obj.name !== "React") return;
+
+ // not createClass
+ var prop = callee.property;
+ if (prop.type !== "Identifier" || prop.name !== "createClass") return;
+
+ // no arguments
+ var args = call.arguments;
+ if (args.length !== 1) return;
+
+ // not an object
+ var first = args[0];
+ if (first.type !== "ObjectExpression") return;
+
+ var props = first.properties;
+ var safe = true;
+
+ _.each(props, function (prop) {
+ if (prop.key.name === "displayName") {
+ return safe = false;
+ }
+ });
+
+ if (safe) {
+ props.unshift(b.property("init", b.identifier("displayName"), b.literal(id)));
+ }
+};
+
+exports.AssignmentExpression =
+exports.Property =
+exports.VariableDeclarator = function (node) {
+ var left, right;
+
+ if (node.type === "AssignmentExpression") {
+ left = node.left;
+ right = node.right;
+ } else if (node.type === "Property") {
+ left = node.key;
+ right = node.value;
+ } else if (node.type === "VariableDeclarator") {
+ left = node.id;
+ right = node.init;
+ }
+
+ if (left && left.type === "MemberExpression") {
+ left = left.property;
+ }
+
+ if (left && left.type === "Identifier") {
+ addDisplayName(left.name, right);
+ }
+};
diff --git a/test/fixtures/syntax/jsx/annotation/actual.js b/test/fixtures/syntax/jsx/annotation/actual.js
new file mode 100644
index 0000000000..2412fb7b42
--- /dev/null
+++ b/test/fixtures/syntax/jsx/annotation/actual.js
@@ -0,0 +1,3 @@
+/** @jsx CUSTOM_DOM */
+
+
diff --git a/test/fixtures/syntax/jsx/annotation/expected.js b/test/fixtures/syntax/jsx/annotation/expected.js
new file mode 100644
index 0000000000..cd478b7097
--- /dev/null
+++ b/test/fixtures/syntax/jsx/annotation/expected.js
@@ -0,0 +1,2 @@
+"use strict";
+CUSTOM_DOM.a(null);
\ No newline at end of file
diff --git a/test/fixtures/syntax/jsx/empty/actual.js b/test/fixtures/syntax/jsx/empty/actual.js
new file mode 100644
index 0000000000..c9f99abcd0
--- /dev/null
+++ b/test/fixtures/syntax/jsx/empty/actual.js
@@ -0,0 +1 @@
+{}
diff --git a/test/fixtures/syntax/jsx/empty/expected.js b/test/fixtures/syntax/jsx/empty/expected.js
new file mode 100644
index 0000000000..4835a2a251
--- /dev/null
+++ b/test/fixtures/syntax/jsx/empty/expected.js
@@ -0,0 +1,2 @@
+"use strict";
+x(null, null);
\ No newline at end of file
diff --git a/test/fixtures/syntax/jsx/everything/actual.js b/test/fixtures/syntax/jsx/everything/actual.js
new file mode 100644
index 0000000000..0c42b3a8af
--- /dev/null
+++ b/test/fixtures/syntax/jsx/everything/actual.js
@@ -0,0 +1,2 @@
+ :
+}>
diff --git a/test/fixtures/syntax/jsx/everything/expected.js b/test/fixtures/syntax/jsx/everything/expected.js
new file mode 100644
index 0000000000..7d39e64fbc
--- /dev/null
+++ b/test/fixtures/syntax/jsx/everything/expected.js
@@ -0,0 +1,7 @@
+"use strict";
+
+X({
+ "data-prop": (x ? Y({
+ prop: 2
+ }) : Z(null, "\n"))
+});
\ No newline at end of file
diff --git a/test/fixtures/syntax/jsx/expressions/actual.js b/test/fixtures/syntax/jsx/expressions/actual.js
new file mode 100644
index 0000000000..8191b76c06
--- /dev/null
+++ b/test/fixtures/syntax/jsx/expressions/actual.js
@@ -0,0 +1,5 @@
+({a});
+
+({a} {b});
+
+();
diff --git a/test/fixtures/syntax/jsx/expressions/expected.js b/test/fixtures/syntax/jsx/expressions/expected.js
new file mode 100644
index 0000000000..d3818d86d3
--- /dev/null
+++ b/test/fixtures/syntax/jsx/expressions/expected.js
@@ -0,0 +1,8 @@
+"use strict";
+X(null, a);
+X(null, [a, " ", b]);
+
+X({
+ prop: a,
+ yes: true
+});
\ No newline at end of file
diff --git a/test/fixtures/syntax/jsx/known-tags/actual.js b/test/fixtures/syntax/jsx/known-tags/actual.js
new file mode 100644
index 0000000000..41ab602321
--- /dev/null
+++ b/test/fixtures/syntax/jsx/known-tags/actual.js
@@ -0,0 +1 @@
+
diff --git a/test/fixtures/syntax/jsx/known-tags/expected.js b/test/fixtures/syntax/jsx/known-tags/expected.js
new file mode 100644
index 0000000000..ec70567216
--- /dev/null
+++ b/test/fixtures/syntax/jsx/known-tags/expected.js
@@ -0,0 +1,2 @@
+"use strict";
+React.DOM.a(null);
\ No newline at end of file
diff --git a/test/fixtures/syntax/jsx/member-expression/actual.js b/test/fixtures/syntax/jsx/member-expression/actual.js
new file mode 100644
index 0000000000..67707269e9
--- /dev/null
+++ b/test/fixtures/syntax/jsx/member-expression/actual.js
@@ -0,0 +1 @@
+
diff --git a/test/fixtures/syntax/jsx/member-expression/expected.js b/test/fixtures/syntax/jsx/member-expression/expected.js
new file mode 100644
index 0000000000..4dea12440d
--- /dev/null
+++ b/test/fixtures/syntax/jsx/member-expression/expected.js
@@ -0,0 +1,2 @@
+"use strict";
+Test.X(null);
\ No newline at end of file
diff --git a/test/fixtures/syntax/jsx/no-xml-namespaces/actual.js b/test/fixtures/syntax/jsx/no-xml-namespaces/actual.js
new file mode 100644
index 0000000000..80e9ac6925
--- /dev/null
+++ b/test/fixtures/syntax/jsx/no-xml-namespaces/actual.js
@@ -0,0 +1 @@
+
diff --git a/test/fixtures/syntax/jsx/no-xml-namespaces/options.json b/test/fixtures/syntax/jsx/no-xml-namespaces/options.json
new file mode 100644
index 0000000000..84921377cb
--- /dev/null
+++ b/test/fixtures/syntax/jsx/no-xml-namespaces/options.json
@@ -0,0 +1,3 @@
+{
+ "throws": "Namespace tags are not supported. ReactJSX is not XML."
+}
diff --git a/test/fixtures/syntax/jsx/self-closing-tags/actual.js b/test/fixtures/syntax/jsx/self-closing-tags/actual.js
new file mode 100644
index 0000000000..c74cd039aa
--- /dev/null
+++ b/test/fixtures/syntax/jsx/self-closing-tags/actual.js
@@ -0,0 +1,3 @@
+();
+
+();
diff --git a/test/fixtures/syntax/jsx/self-closing-tags/expected.js b/test/fixtures/syntax/jsx/self-closing-tags/expected.js
new file mode 100644
index 0000000000..3ebb174fab
--- /dev/null
+++ b/test/fixtures/syntax/jsx/self-closing-tags/expected.js
@@ -0,0 +1,6 @@
+"use strict";
+X(null);
+
+X({
+ prop: "1"
+});
\ No newline at end of file
diff --git a/test/fixtures/syntax/jsx/simple-tags/actual.js b/test/fixtures/syntax/jsx/simple-tags/actual.js
new file mode 100644
index 0000000000..87779ff5c6
--- /dev/null
+++ b/test/fixtures/syntax/jsx/simple-tags/actual.js
@@ -0,0 +1 @@
+
diff --git a/test/fixtures/syntax/jsx/simple-tags/expected.js b/test/fixtures/syntax/jsx/simple-tags/expected.js
new file mode 100644
index 0000000000..f38433898c
--- /dev/null
+++ b/test/fixtures/syntax/jsx/simple-tags/expected.js
@@ -0,0 +1,2 @@
+"use strict";
+X(null);
\ No newline at end of file
diff --git a/test/fixtures/syntax/jsx/tags-with-children/actual.js b/test/fixtures/syntax/jsx/tags-with-children/actual.js
new file mode 100644
index 0000000000..aa8ed7080b
--- /dev/null
+++ b/test/fixtures/syntax/jsx/tags-with-children/actual.js
@@ -0,0 +1,3 @@
+();
+
+();
diff --git a/test/fixtures/syntax/jsx/tags-with-children/expected.js b/test/fixtures/syntax/jsx/tags-with-children/expected.js
new file mode 100644
index 0000000000..1183f4a58a
--- /dev/null
+++ b/test/fixtures/syntax/jsx/tags-with-children/expected.js
@@ -0,0 +1,9 @@
+"use strict";
+
+X({
+ prop: "2"
+}, Y(null));
+
+X({
+ prop: "2"
+}, [Y(null), Z(null)]);
\ No newline at end of file
diff --git a/test/fixtures/syntax/jsx/tags-with-literals/actual.js b/test/fixtures/syntax/jsx/tags-with-literals/actual.js
new file mode 100644
index 0000000000..5507ef2680
--- /dev/null
+++ b/test/fixtures/syntax/jsx/tags-with-literals/actual.js
@@ -0,0 +1,13 @@
+( );
+
+(
+);
+
+(
+ string
+);
+
+(
+ string
+ string
+ );
diff --git a/test/fixtures/syntax/jsx/tags-with-literals/expected.js b/test/fixtures/syntax/jsx/tags-with-literals/expected.js
new file mode 100644
index 0000000000..956943003e
--- /dev/null
+++ b/test/fixtures/syntax/jsx/tags-with-literals/expected.js
@@ -0,0 +1,5 @@
+"use strict";
+X(null, " ");
+X(null, "\n");
+X(null, "\n string\n");
+X(null, "\n string\n string\n ");
\ No newline at end of file
diff --git a/test/fixtures/syntax/react/display-name-assignment-expression/actual.js b/test/fixtures/syntax/react/display-name-assignment-expression/actual.js
new file mode 100644
index 0000000000..f3affde052
--- /dev/null
+++ b/test/fixtures/syntax/react/display-name-assignment-expression/actual.js
@@ -0,0 +1,6 @@
+var Component;
+Component = React.createClass({
+ render: function() {
+ return null;
+ }
+});
diff --git a/test/fixtures/syntax/react/display-name-assignment-expression/expected.js b/test/fixtures/syntax/react/display-name-assignment-expression/expected.js
new file mode 100644
index 0000000000..0096748e3d
--- /dev/null
+++ b/test/fixtures/syntax/react/display-name-assignment-expression/expected.js
@@ -0,0 +1,10 @@
+"use strict";
+var Component;
+
+Component = React.createClass({
+ displayName: "Component",
+
+ render: function() {
+ return null;
+ }
+});
\ No newline at end of file
diff --git a/test/fixtures/syntax/react/display-name-if-missing/actual.js b/test/fixtures/syntax/react/display-name-if-missing/actual.js
new file mode 100644
index 0000000000..4232881d66
--- /dev/null
+++ b/test/fixtures/syntax/react/display-name-if-missing/actual.js
@@ -0,0 +1,6 @@
+var Whateva = React.createClass({
+ displayName: "Whatever",
+ render: function() {
+ return null;
+ }
+});
diff --git a/test/fixtures/syntax/react/display-name-if-missing/expected.js b/test/fixtures/syntax/react/display-name-if-missing/expected.js
new file mode 100644
index 0000000000..e802413800
--- /dev/null
+++ b/test/fixtures/syntax/react/display-name-if-missing/expected.js
@@ -0,0 +1,9 @@
+"use strict";
+
+var Whateva = React.createClass({
+ displayName: "Whatever",
+
+ render: function() {
+ return null;
+ }
+});
\ No newline at end of file
diff --git a/test/fixtures/syntax/react/display-name-object-declaration/actual.js b/test/fixtures/syntax/react/display-name-object-declaration/actual.js
new file mode 100644
index 0000000000..51a000a533
--- /dev/null
+++ b/test/fixtures/syntax/react/display-name-object-declaration/actual.js
@@ -0,0 +1,7 @@
+exports = {
+ Component: React.createClass({
+ render: function() {
+ return null;
+ }
+ })
+};
diff --git a/test/fixtures/syntax/react/display-name-object-declaration/expected.js b/test/fixtures/syntax/react/display-name-object-declaration/expected.js
new file mode 100644
index 0000000000..cde3afa785
--- /dev/null
+++ b/test/fixtures/syntax/react/display-name-object-declaration/expected.js
@@ -0,0 +1,11 @@
+"use strict";
+
+exports = {
+ Component: React.createClass({
+ displayName: "Component",
+
+ render: function() {
+ return null;
+ }
+ })
+};
\ No newline at end of file
diff --git a/test/fixtures/syntax/react/display-name-property-assignment/actual.js b/test/fixtures/syntax/react/display-name-property-assignment/actual.js
new file mode 100644
index 0000000000..f856d33f07
--- /dev/null
+++ b/test/fixtures/syntax/react/display-name-property-assignment/actual.js
@@ -0,0 +1,5 @@
+exports.Component = React.createClass({
+ render: function() {
+ return null;
+ }
+});
diff --git a/test/fixtures/syntax/react/display-name-property-assignment/expected.js b/test/fixtures/syntax/react/display-name-property-assignment/expected.js
new file mode 100644
index 0000000000..ca445b30a1
--- /dev/null
+++ b/test/fixtures/syntax/react/display-name-property-assignment/expected.js
@@ -0,0 +1,9 @@
+"use strict";
+
+exports.Component = React.createClass({
+ displayName: "Component",
+
+ render: function() {
+ return null;
+ }
+});
\ No newline at end of file
diff --git a/test/fixtures/syntax/react/display-name-variable-declaration/actual.js b/test/fixtures/syntax/react/display-name-variable-declaration/actual.js
new file mode 100644
index 0000000000..e751186def
--- /dev/null
+++ b/test/fixtures/syntax/react/display-name-variable-declaration/actual.js
@@ -0,0 +1,5 @@
+var Component = React.createClass({
+ render: function() {
+ return null;
+ }
+});
diff --git a/test/fixtures/syntax/react/display-name-variable-declaration/expected.js b/test/fixtures/syntax/react/display-name-variable-declaration/expected.js
new file mode 100644
index 0000000000..60d186b8dd
--- /dev/null
+++ b/test/fixtures/syntax/react/display-name-variable-declaration/expected.js
@@ -0,0 +1,9 @@
+"use strict";
+
+var Component = React.createClass({
+ displayName: "Component",
+
+ render: function() {
+ return null;
+ }
+});
\ No newline at end of file