traceur
Version:
ES6 to ES5 compiler
217 lines (200 loc) • 6.42 kB
JavaScript
// Copyright 2015 Traceur Authors.
//
// 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.
import {
JSX_ELEMENT,
JSX_PLACEHOLDER,
JSX_SPREAD_ATTRIBUTE,
JSX_TEXT,
LITERAL_EXPRESSION,
} from '../syntax/trees/ParseTreeType.js';
import {
JsxText,
LiteralExpression,
LiteralPropertyName,
SpreadExpression,
} from '../syntax/trees/ParseTrees.js';
import {LiteralToken} from '../syntax/LiteralToken.js';
import {ParseTreeTransformer} from './ParseTreeTransformer.js';
import {STRING} from '../syntax/TokenType.js';
import {
createArgumentList,
createIdentifierExpression,
createIdentifierToken,
createMemberExpression,
createNullLiteral,
createObjectLiteral,
createPropertyNameAssignment,
createStringLiteral,
createStringLiteralToken,
createTrueLiteral,
} from './ParseTreeFactory.js';
import {parseExpression} from './PlaceholderParser.js';
import {spreadProperties} from './SpreadPropertiesTransformer.js';
import ImportRuntimeTrait from './ImportRuntimeTrait.js';
/**
* Desugars JSX expressions.
*
* <p a="b" c="d">e{f}g</p>
*
* to:
*
* Rect.createElement('p', {a: 'b', c: 'd'}, 'e', f, 'g')
*
* The emitted function is configurable. By default the generated function is
* `React.createElement` but by setting the `jsx` option you can provide your
* own function to use:
*
* // Options: --jsx=myFunc
* <p/>
*
* Would generate something like:
*
* myFunc('p', null)
*/
export class JsxTransformer extends ImportRuntimeTrait(ParseTreeTransformer) {
constructor(idGen, reporter, options) {
super();
this.options = options;
this.jsxFunction_ = null;
}
getJsxFunction_() {
// Let the emitted JSX function be configurable.
// --jsx -> React.createElement(tagName, opts, ...children)
// --jsx=a.b.c -> a.b.c(tagName, opts, ...children)
if (!this.jsxFunction_) {
let jsx = this.options.jsx;
if (typeof jsx === 'string') {
this.jsxFunction_ = parseExpression([jsx]);
} else {
this.jsxFunction_ = parseExpression `React.createElement`;
}
}
return this.jsxFunction_;
}
transformJsxElement(tree) {
let name = this.transformAny(tree.name);
let props = this.transformJsxAttributes_(tree);
let children = this.transformJsxChildren_(tree.children);
let args = createArgumentList([name, props, ...children]);
return parseExpression `${this.getJsxFunction_()}(${args})`;
}
transformJsxAttributes_(tree) {
let attrs = this.transformList(tree.attributes);
if (attrs.length === 0) {
return createNullLiteral();
}
if (tree.attributes.some(a => a.type === JSX_SPREAD_ATTRIBUTE)) {
// <a b='b' c='c' {...d} {...g} />
// =>
// React.createElement('a',
// $traceurRuntime.spreadProperties({b: 'b', c: 'c'}, d, g))
return spreadProperties(attrs, this);
}
return createObjectLiteral(attrs);
}
transformJsxElementName(tree) {
if (tree.names.length === 1) {
let {value} = tree.names[0];
if (value[0] === value[0].toUpperCase()) {
return createIdentifierExpression(value);;
}
return createStringLiteral(value);
}
let names = tree.names.map(jsxIdentifierToToken);
let operand = names[0];
if (operand.type === STRING) {
names[0] = new LiteralExpression(operand.location, operand);
}
return createMemberExpression(...names);
}
transformJsxAttribute(tree) {
let name =
new LiteralPropertyName(tree.name.location,
jsxIdentifierToToken(tree.name));
let value;
if (tree.value === null) {
value = createTrueLiteral();
} else if (tree.value.type === LITERAL_EXPRESSION) {
const {literalToken} = tree.value;
const v = literalToken.value;
const {location} = literalToken;
const lit =
new LiteralToken(STRING, normalizeAttributeValue(v), location);
value = new LiteralExpression(location, lit);
} else {
value = this.transformAny(tree.value);
}
return createPropertyNameAssignment(name, value);
}
transformJsxPlaceholder(tree) {
return this.transformAny(tree.expression);
}
transformJsxSpreadAttribute(tree) {
return new SpreadExpression(tree.location,
this.transformAny(tree.expression));
}
transformJsxText(tree) {
return createStringLiteral(tree.value.value);
}
transformJsxChildren_(trees) {
// All lines having leading or trailing whitespace are trimmed, all newlines
// are removed, adjacent text separated by newlines become separated by a
// single space. Any whitespace tabs are replaced with spaces. Strings
// inside expressions are unaffected.
let rv = [];
trees.forEach(tree => {
let newTree;
switch (tree.type) {
case JSX_ELEMENT:
newTree = this.transformAny(tree);
break;
case JSX_PLACEHOLDER:
if (tree.expression === null) {
return;
}
newTree = this.transformAny(tree);
break;
case JSX_TEXT: {
let s = tree.value.value;
s = s.replace(/\t/g, ' ');
if (!/[\n\r]/.test(s)) {
newTree = createStringLiteral(s);
} else {
s = s.replace(/^[ \t]*[\n\r]\s*/, '');
s = s.replace(/[ \t]*[\n\r]\s*$/, '');
if (s === '') {
return;
}
newTree = createStringLiteral(s);
}
break;
}
}
rv.push(newTree)
});
return rv;
}
}
function jsxIdentifierToToken(token) {
let value = token.value;
if (value.indexOf('-') !== -1) {
return createStringLiteralToken(value);
}
return createIdentifierToken(value);
}
function normalizeAttributeValue(s) {
return JSON.stringify(
s.slice(1, -1). // remove the quotes
replace(/\n\s+/g, ' '));
}