123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- /**
- * Copyright 2013-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- */
- 'use strict';
- /*global exports:true*/
- var Syntax = require('esprima-fb').Syntax;
- var utils = require('../src/utils');
- var jsxHelpers = require('./jsx-helpers');
- var renderJSXExpressionContainer = jsxHelpers.renderJSXExpressionContainer;
- var renderJSXLiteral = jsxHelpers.renderJSXLiteral;
- var quoteAttrName = jsxHelpers.quoteAttrName;
- var trimLeft = jsxHelpers.trimLeft;
- /**
- * Customized desugar processor for React JSX. Currently:
- *
- * <X> </X> => React.createElement(X, null)
- * <X prop="1" /> => React.createElement(X, {prop: '1'}, null)
- * <X prop="2"><Y /></X> => React.createElement(X, {prop:'2'},
- * React.createElement(Y, null)
- * )
- * <div /> => React.createElement("div", null)
- */
- /**
- * Removes all non-whitespace/parenthesis characters
- */
- var reNonWhiteParen = /([^\s\(\)])/g;
- function stripNonWhiteParen(value) {
- return value.replace(reNonWhiteParen, '');
- }
- var tagConvention = /^[a-z]|\-/;
- function isTagName(name) {
- return tagConvention.test(name);
- }
- function visitReactTag(traverse, object, path, state) {
- var openingElement = object.openingElement;
- var nameObject = openingElement.name;
- var attributesObject = openingElement.attributes;
- utils.catchup(openingElement.range[0], state, trimLeft);
- if (nameObject.type === Syntax.JSXNamespacedName && nameObject.namespace) {
- throw new Error('Namespace tags are not supported. ReactJSX is not XML.');
- }
- // We assume that the React runtime is already in scope
- utils.append('React.createElement(', state);
- if (nameObject.type === Syntax.JSXIdentifier && isTagName(nameObject.name)) {
- utils.append('"' + nameObject.name + '"', state);
- utils.move(nameObject.range[1], state);
- } else {
- // Use utils.catchup in this case so we can easily handle
- // JSXMemberExpressions which look like Foo.Bar.Baz. This also handles
- // JSXIdentifiers that aren't fallback tags.
- utils.move(nameObject.range[0], state);
- utils.catchup(nameObject.range[1], state);
- }
- utils.append(', ', state);
- var hasAttributes = attributesObject.length;
- var hasAtLeastOneSpreadProperty = attributesObject.some(function(attr) {
- return attr.type === Syntax.JSXSpreadAttribute;
- });
- // if we don't have any attributes, pass in null
- if (hasAtLeastOneSpreadProperty) {
- utils.append('React.__spread({', state);
- } else if (hasAttributes) {
- utils.append('{', state);
- } else {
- utils.append('null', state);
- }
- // keep track of if the previous attribute was a spread attribute
- var previousWasSpread = false;
- // write attributes
- attributesObject.forEach(function(attr, index) {
- var isLast = index === attributesObject.length - 1;
- if (attr.type === Syntax.JSXSpreadAttribute) {
- // Close the previous object or initial object
- if (!previousWasSpread) {
- utils.append('}, ', state);
- }
- // Move to the expression start, ignoring everything except parenthesis
- // and whitespace.
- utils.catchup(attr.range[0], state, stripNonWhiteParen);
- // Plus 1 to skip `{`.
- utils.move(attr.range[0] + 1, state);
- utils.catchup(attr.argument.range[0], state, stripNonWhiteParen);
- traverse(attr.argument, path, state);
- utils.catchup(attr.argument.range[1], state);
- // Move to the end, ignoring parenthesis and the closing `}`
- utils.catchup(attr.range[1] - 1, state, stripNonWhiteParen);
- if (!isLast) {
- utils.append(', ', state);
- }
- utils.move(attr.range[1], state);
- previousWasSpread = true;
- return;
- }
- // If the next attribute is a spread, we're effective last in this object
- if (!isLast) {
- isLast = attributesObject[index + 1].type === Syntax.JSXSpreadAttribute;
- }
- if (attr.name.namespace) {
- throw new Error(
- 'Namespace attributes are not supported. ReactJSX is not XML.');
- }
- var name = attr.name.name;
- utils.catchup(attr.range[0], state, trimLeft);
- if (previousWasSpread) {
- utils.append('{', state);
- }
- utils.append(quoteAttrName(name), state);
- utils.append(': ', state);
- if (!attr.value) {
- state.g.buffer += 'true';
- state.g.position = attr.name.range[1];
- if (!isLast) {
- utils.append(', ', state);
- }
- } else {
- utils.move(attr.name.range[1], state);
- // Use catchupNewlines to skip over the '=' in the attribute
- utils.catchupNewlines(attr.value.range[0], state);
- if (attr.value.type === Syntax.Literal) {
- renderJSXLiteral(attr.value, isLast, state);
- } else {
- renderJSXExpressionContainer(traverse, attr.value, isLast, path, state);
- }
- }
- utils.catchup(attr.range[1], state, trimLeft);
- previousWasSpread = false;
- });
- if (!openingElement.selfClosing) {
- utils.catchup(openingElement.range[1] - 1, state, trimLeft);
- utils.move(openingElement.range[1], state);
- }
- if (hasAttributes && !previousWasSpread) {
- utils.append('}', state);
- }
- if (hasAtLeastOneSpreadProperty) {
- utils.append(')', state);
- }
- // filter out whitespace
- var childrenToRender = object.children.filter(function(child) {
- return !(child.type === Syntax.Literal
- && typeof child.value === 'string'
- && child.value.match(/^[ \t]*[\r\n][ \t\r\n]*$/));
- });
- if (childrenToRender.length > 0) {
- var lastRenderableIndex;
- childrenToRender.forEach(function(child, index) {
- if (child.type !== Syntax.JSXExpressionContainer ||
- child.expression.type !== Syntax.JSXEmptyExpression) {
- lastRenderableIndex = index;
- }
- });
- if (lastRenderableIndex !== undefined) {
- utils.append(', ', state);
- }
- childrenToRender.forEach(function(child, index) {
- utils.catchup(child.range[0], state, trimLeft);
- var isLast = index >= lastRenderableIndex;
- if (child.type === Syntax.Literal) {
- renderJSXLiteral(child, isLast, state);
- } else if (child.type === Syntax.JSXExpressionContainer) {
- renderJSXExpressionContainer(traverse, child, isLast, path, state);
- } else {
- traverse(child, path, state);
- if (!isLast) {
- utils.append(', ', state);
- }
- }
- utils.catchup(child.range[1], state, trimLeft);
- });
- }
- if (openingElement.selfClosing) {
- // everything up to />
- utils.catchup(openingElement.range[1] - 2, state, trimLeft);
- utils.move(openingElement.range[1], state);
- } else {
- // everything up to </ sdflksjfd>
- utils.catchup(object.closingElement.range[0], state, trimLeft);
- utils.move(object.closingElement.range[1], state);
- }
- utils.append(')', state);
- return false;
- }
- visitReactTag.test = function(object, path, state) {
- return object.type === Syntax.JSXElement;
- };
- exports.visitorList = [
- visitReactTag
- ];
|