fis3-parser-rosetta
Version:
FIS parser for rosetta
223 lines (175 loc) • 6.58 kB
JavaScript
var Syntax = require('esprima-fb').Syntax;
var utils = require('jstransform/src/utils');
var jsxHelpers = require('jstransform/visitors/jsx-helpers');
var renderJSXExpressionContainer = jsxHelpers.renderJSXExpressionContainer;
var renderJSXLiteral = jsxHelpers.renderJSXLiteral;
var quoteAttrName = jsxHelpers.quoteAttrName;
var trimLeft = jsxHelpers.trimLeft;
var reNonWhiteParen = /([^\s\(\)])/g;
function stripNonWhiteParen(value) {
return value.replace(reNonWhiteParen, '');
}
var tagConvention = /^[a-z||A-Z]|\-/;
function isTagName(name) {
return tagConvention.test(name);
}
function visitReactTag(traverse, object, path, state) {
var options = state.g.opts;
var alaisMap = options.alias || {};
var openingElement = object.openingElement;
var nameObject = openingElement.name;
var attributesObject = openingElement.attributes;
function alias(origin) {
return alaisMap[origin] || origin;
}
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.');
}
utils.append(alias('Rosetta.create') + '(', 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(alias('Rosetta.__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('\''+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
];