react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / react-tools / vendor / fbtransform / transforms / react.js
81158 views/**1* Copyright 2013-2014, Facebook, Inc.2* All rights reserved.3*4* This source code is licensed under the BSD-style license found in the5* LICENSE file in the root directory of this source tree. An additional grant6* of patent rights can be found in the PATENTS file in the same directory.7*/8/*global exports:true*/9"use strict";1011var Syntax = require('jstransform').Syntax;12var utils = require('jstransform/src/utils');1314var FALLBACK_TAGS = require('./xjs').knownTags;15var renderXJSExpressionContainer =16require('./xjs').renderXJSExpressionContainer;17var renderXJSLiteral = require('./xjs').renderXJSLiteral;18var quoteAttrName = require('./xjs').quoteAttrName;1920var trimLeft = require('./xjs').trimLeft;2122/**23* Customized desugar processor for React JSX. Currently:24*25* <X> </X> => React.createElement(X, null)26* <X prop="1" /> => React.createElement(X, {prop: '1'}, null)27* <X prop="2"><Y /></X> => React.createElement(X, {prop:'2'},28* React.createElement(Y, null)29* )30* <div /> => React.createElement("div", null)31*/3233/**34* Removes all non-whitespace/parenthesis characters35*/36var reNonWhiteParen = /([^\s\(\)])/g;37function stripNonWhiteParen(value) {38return value.replace(reNonWhiteParen, '');39}4041var tagConvention = /^[a-z]|\-/;42function isTagName(name) {43return tagConvention.test(name);44}4546function visitReactTag(traverse, object, path, state) {47var openingElement = object.openingElement;48var nameObject = openingElement.name;49var attributesObject = openingElement.attributes;5051utils.catchup(openingElement.range[0], state, trimLeft);5253if (nameObject.type === Syntax.XJSNamespacedName && nameObject.namespace) {54throw new Error('Namespace tags are not supported. ReactJSX is not XML.');55}5657// We assume that the React runtime is already in scope58utils.append('React.createElement(', state);5960// Identifiers with lower case or hypthens are fallback tags (strings).61// XJSMemberExpressions are not.62if (nameObject.type === Syntax.XJSIdentifier && isTagName(nameObject.name)) {63// This is a temporary error message to assist upgrades64if (!FALLBACK_TAGS.hasOwnProperty(nameObject.name)) {65throw new Error(66'Lower case component names (' + nameObject.name + ') are no longer ' +67'supported in JSX: See http://fb.me/react-jsx-lower-case'68);69}7071utils.append('"' + nameObject.name + '"', state);72utils.move(nameObject.range[1], state);73} else {74// Use utils.catchup in this case so we can easily handle75// XJSMemberExpressions which look like Foo.Bar.Baz. This also handles76// XJSIdentifiers that aren't fallback tags.77utils.move(nameObject.range[0], state);78utils.catchup(nameObject.range[1], state);79}8081utils.append(', ', state);8283var hasAttributes = attributesObject.length;8485var hasAtLeastOneSpreadProperty = attributesObject.some(function(attr) {86return attr.type === Syntax.XJSSpreadAttribute;87});8889// if we don't have any attributes, pass in null90if (hasAtLeastOneSpreadProperty) {91utils.append('React.__spread({', state);92} else if (hasAttributes) {93utils.append('{', state);94} else {95utils.append('null', state);96}9798// keep track of if the previous attribute was a spread attribute99var previousWasSpread = false;100101// write attributes102attributesObject.forEach(function(attr, index) {103var isLast = index === attributesObject.length - 1;104105if (attr.type === Syntax.XJSSpreadAttribute) {106// Close the previous object or initial object107if (!previousWasSpread) {108utils.append('}, ', state);109}110111// Move to the expression start, ignoring everything except parenthesis112// and whitespace.113utils.catchup(attr.range[0], state, stripNonWhiteParen);114// Plus 1 to skip `{`.115utils.move(attr.range[0] + 1, state);116utils.catchup(attr.argument.range[0], state, stripNonWhiteParen);117118traverse(attr.argument, path, state);119120utils.catchup(attr.argument.range[1], state);121122// Move to the end, ignoring parenthesis and the closing `}`123utils.catchup(attr.range[1] - 1, state, stripNonWhiteParen);124125if (!isLast) {126utils.append(', ', state);127}128129utils.move(attr.range[1], state);130131previousWasSpread = true;132133return;134}135136// If the next attribute is a spread, we're effective last in this object137if (!isLast) {138isLast = attributesObject[index + 1].type === Syntax.XJSSpreadAttribute;139}140141if (attr.name.namespace) {142throw new Error(143'Namespace attributes are not supported. ReactJSX is not XML.');144}145var name = attr.name.name;146147utils.catchup(attr.range[0], state, trimLeft);148149if (previousWasSpread) {150utils.append('{', state);151}152153utils.append(quoteAttrName(name), state);154utils.append(': ', state);155156if (!attr.value) {157state.g.buffer += 'true';158state.g.position = attr.name.range[1];159if (!isLast) {160utils.append(', ', state);161}162} else {163utils.move(attr.name.range[1], state);164// Use catchupNewlines to skip over the '=' in the attribute165utils.catchupNewlines(attr.value.range[0], state);166if (attr.value.type === Syntax.Literal) {167renderXJSLiteral(attr.value, isLast, state);168} else {169renderXJSExpressionContainer(traverse, attr.value, isLast, path, state);170}171}172173utils.catchup(attr.range[1], state, trimLeft);174175previousWasSpread = false;176177});178179if (!openingElement.selfClosing) {180utils.catchup(openingElement.range[1] - 1, state, trimLeft);181utils.move(openingElement.range[1], state);182}183184if (hasAttributes && !previousWasSpread) {185utils.append('}', state);186}187188if (hasAtLeastOneSpreadProperty) {189utils.append(')', state);190}191192// filter out whitespace193var childrenToRender = object.children.filter(function(child) {194return !(child.type === Syntax.Literal195&& typeof child.value === 'string'196&& child.value.match(/^[ \t]*[\r\n][ \t\r\n]*$/));197});198if (childrenToRender.length > 0) {199var lastRenderableIndex;200201childrenToRender.forEach(function(child, index) {202if (child.type !== Syntax.XJSExpressionContainer ||203child.expression.type !== Syntax.XJSEmptyExpression) {204lastRenderableIndex = index;205}206});207208if (lastRenderableIndex !== undefined) {209utils.append(', ', state);210}211212childrenToRender.forEach(function(child, index) {213utils.catchup(child.range[0], state, trimLeft);214215var isLast = index >= lastRenderableIndex;216217if (child.type === Syntax.Literal) {218renderXJSLiteral(child, isLast, state);219} else if (child.type === Syntax.XJSExpressionContainer) {220renderXJSExpressionContainer(traverse, child, isLast, path, state);221} else {222traverse(child, path, state);223if (!isLast) {224utils.append(', ', state);225}226}227228utils.catchup(child.range[1], state, trimLeft);229});230}231232if (openingElement.selfClosing) {233// everything up to />234utils.catchup(openingElement.range[1] - 2, state, trimLeft);235utils.move(openingElement.range[1], state);236} else {237// everything up to </ sdflksjfd>238utils.catchup(object.closingElement.range[0], state, trimLeft);239utils.move(object.closingElement.range[1], state);240}241242utils.append(')', state);243return false;244}245246visitReactTag.test = function(object, path, state) {247return object.type === Syntax.XJSElement;248};249250exports.visitorList = [251visitReactTag252];253254255