| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296 |
1x
1x
1x
1x
164x
1x
113x
113x
113x
1x
58x
97x
97x
21x
76x
4x
72x
1x
124x
301x
1x
62x
1x
124x
1x
62x
62x
62x
62x
62x
62x
1x
62x
39x
62x
6x
62x
17x
62x
3x
3x
59x
1x
45x
94x
14x
80x
69x
11x
2x
9x
9x
1x
149x
149x
149x
149x
149x
2x
147x
149x
149x
22x
2x
22x
4x
22x
127x
3x
124x
29x
69x
26x
1x
4x
4x
4x
1x
34x
34x
4x
10x
10x
4x
10x
4x
4x
4x
4x
30x
30x
1x
124x
124x
124x
124x
124x
83x
83x
83x
83x
41x
41x
41x
1x
95x
95x
95x
58x
58x
58x
58x
97x
58x
4x
10x
4x
4x
4x
54x
37x
36x
1x
1x
1x
26x
26x
1x
80x
80x
80x
3x
80x
| "use strict";
const t = require("babel-core").types;
const getTagAndClassNamesAndId = require("./utils").getTagAndClassNamesAndId;
let isCssModules = false;
// utility functions that starts with b means build.
const isHyperscriptCall = node =>
t.isIdentifier(node.callee, {
name: "h"
});
const bJsxAttr = (prop, expressionOrValue) => {
const attributeName = t.isStringLiteral(prop)
? prop.extra.rawValue
: prop.name;
const stringOrExpression = t.isStringLiteral(expressionOrValue)
? expressionOrValue
: t.JSXExpressionContainer(expressionOrValue);
return t.JSXAttribute(bJsxIdent(attributeName), stringOrExpression);
};
const bJsxAttributes = objectExpression => {
return objectExpression.properties.map(node => {
const { key, value, argument } = node;
if (t.isSpreadProperty(node) || t.isSpreadElement(node)) {
return t.JSXSpreadAttribute(argument);
} else if (t.isProperty(node) && node.computed && !t.isStringLiteral(key)) {
// to handle h(Abc, { [kek]: 0, ["norm"]: 1 }) to <Abc {...{ [kek]: 0 }} norm={1} />
return t.JSXSpreadAttribute(t.objectExpression([node]));
} else {
return bJsxAttr(key, value);
}
});
};
const bJsxOpenElem = ({ name, selfClosing = false, attributes = [] }) =>
t.JSXOpeningElement(
t.isJSXMemberExpression(name) ? name : bJsxIdent(name),
attributes,
selfClosing
);
const bJsxIdent = name => t.JSXIdentifier(name);
const bJsxCloseElem = name =>
t.isJSXMemberExpression(name)
? t.JSXClosingElement(name)
: t.JSXClosingElement(bJsxIdent(name));
// Builds self closed element
const bJsxElem = ({
name = "div",
attributes = [],
children = [],
selfClosing = false
}) =>
t.JSXElement(
bJsxOpenElem({ attributes, name, selfClosing }),
selfClosing ? null : bJsxCloseElem(name),
children,
selfClosing
);
// Makes component wrap around his children, so closes it around strings/JSXElements/expressions.
const closeComponent = (jsxElem, children) => {
const { name } = jsxElem.openingElement;
jsxElem.selfClosing = false;
jsxElem.openingElement.selfClosing = false;
jsxElem.closingElement = bJsxCloseElem(
t.isJSXMemberExpression(name) ? name : name.name
);
jsxElem.children = children;
return jsxElem;
};
const injectChildren = (jsxElem, node) => {
let result;
if (t.isArrayExpression(node)) {
result = transformChildrenArray(node);
}
if (t.isStringLiteral(node)) {
result = [t.JSXText(node.value)];
}
if (t.isExpression(node) && !result) {
result = [t.JSXExpressionContainer(node)];
}
if (t.isJSXExpressionContainer(jsxElem)) {
closeComponent(jsxElem.expression.right, result);
return jsxElem;
} else {
return closeComponent(jsxElem, result);
}
};
const transformChildrenArray = (node, noExpressionContainers) => {
return node.elements.map(element => {
// Ugliest hack I ever wrote, but this is to avoid putting computed hyperscript calls into the JSXExpressionContainer for ignored computed root h calls
if (
((t.isJSXElement(element) ||
t.isLogicalExpression(element) ||
t.isConditionalExpression(element) ||
t.isMemberExpression(element) ||
t.isIdentifier(element)) &&
noExpressionContainers) ||
(isHyperscriptCall(element) &&
noExpressionContainers &&
element.arguments &&
element.arguments[0] &&
(element.arguments[0].computed ||
(t.isTemplateLiteral(element.arguments[0]) &&
element.arguments[0].expressions.length > 0) ||
(element.arguments[0].arguments &&
element.arguments[0].arguments.length >= 0)))
) {
return element;
}
if (isHyperscriptCall(element)) {
return transformHyperscriptToJsx(element, false);
}
if (t.isStringLiteral(element)) {
return t.JSXText(element.value);
}
Eif (t.isExpression(element)) {
return t.JSXExpressionContainer(element);
}
});
};
const transformHyperscriptToJsx = (node, isTopLevelCall) => {
const [firstArg, secondArg, thirdArg] = node.arguments;
// Handling few corner cases down here
// Handling of h(obj[field]) and h(`stuff ${computed}`) to ignore and convert to StringLiteral if possible
const isTemplateLiteral = t.isTemplateLiteral(firstArg);
const hasExpressions = isTemplateLiteral && firstArg.expressions.length;
const isComputedClassNameOrComponent =
firstArg.computed || hasExpressions || t.isBinaryExpression(firstArg);
// Intermediate value to convert to StringLiteral if TemplateLiteral has no expressions
let firstArgument;
if (isTemplateLiteral && !hasExpressions) {
firstArgument = t.stringLiteral(firstArg.quasis[0].value.raw);
} else {
firstArgument = firstArg;
}
const isFirstArgIsCalledFunction =
firstArg.arguments && firstArg.arguments.length >= 0;
if (
(isComputedClassNameOrComponent || isFirstArgIsCalledFunction) &&
isTopLevelCall
) {
// If top level call just keep node as is
if (t.isArrayExpression(secondArg) && !thirdArg) {
secondArg.elements = transformChildrenArray(secondArg, true);
}
if (t.isArrayExpression(thirdArg)) {
// This will recursively process all children nodes to get JSX/Expressions array
// Second parameter is for ugly hack :P
thirdArg.elements = transformChildrenArray(thirdArg, true);
}
return node;
} else if (isComputedClassNameOrComponent || isFirstArgIsCalledFunction) {
// If nested in JSX wrap in expression container
return t.JSXExpressionContainer(node);
}
switch (node.arguments.length) {
case 1:
return singleArgumentCase(firstArgument);
case 2:
return twoArgumentsCase(firstArgument, secondArg, true);
case 3:
return threeArgumentsCase(firstArgument, secondArg, thirdArg);
default:
break;
}
};
const memberExpressionToJsx = memberExpression => {
const object = t.isMemberExpression(memberExpression.object)
? memberExpressionToJsx(memberExpression.object)
: bJsxIdent(memberExpression.object.name);
const property = bJsxIdent(memberExpression.property.name);
return t.jSXMemberExpression(object, property);
};
const processClassName = className => {
const classNames = className.split(" ");
if (classNames.length > 1) {
const expressions = classNames.map(clazz => {
const isComputed = clazz.includes("-");
return t.MemberExpression(
t.identifier("styles"),
isComputed ? t.stringLiteral(clazz) : t.identifier(clazz),
isComputed
);
});
const quasis = classNames.map(() =>
t.templateElement({ cooked: " ", raw: " " })
); // Spaces between
quasis.pop();
quasis.push(t.templateElement({ cooked: "", raw: "" }, true)); // End of template string
quasis.unshift(t.templateElement({ cooked: "", raw: "" })); // Empty string before
return t.templateLiteral(quasis, expressions);
} else {
const isComputed = className.includes("-");
return isCssModules
? t.MemberExpression(
t.identifier("styles"),
isComputed ? t.stringLiteral(className) : t.identifier(className),
isComputed
)
: t.StringLiteral(className);
}
};
const singleArgumentCase = firstArgument => {
const isElement = t.isStringLiteral(firstArgument);
const isReactComponent = t.isIdentifier(firstArgument);
const isMemberExpression = t.isMemberExpression(firstArgument);
let attributes = [];
if (isElement) {
const { id, className, tag } = getTagAndClassNamesAndId(
firstArgument.value
);
className &&
attributes.push(
bJsxAttr(t.JSXIdentifier("className"), processClassName(className))
);
id && attributes.push(bJsxAttr(t.JSXIdentifier("id"), t.StringLiteral(id)));
return bJsxElem({
selfClosing: true,
name: tag,
attributes
});
} else Eif (isReactComponent || isMemberExpression) {
const componentName = isMemberExpression
? memberExpressionToJsx(firstArgument)
: firstArgument.name;
return bJsxElem({
selfClosing: true,
name: componentName
});
}
};
const twoArgumentsCase = (firstArg, secondArg, thirdArgIsAbsent) => {
const jsxElem = singleArgumentCase(firstArg);
const isPropsObject = t.isObjectExpression(secondArg);
if (isPropsObject) {
const props = bJsxAttributes(secondArg);
const currentProps = jsxElem.openingElement.attributes;
jsxElem.openingElement.attributes = [...currentProps, ...props];
const isShouldBePrepended = props.find(
prop => prop.name && prop.name.name === "shouldRender"
);
if (isShouldBePrepended) {
const shouldRenderExpression = props.find(
prop => prop.name.name === "shouldRender"
).value.expression;
const logicalExpression = t.LogicalExpression(
"&&",
shouldRenderExpression,
jsxElem
);
const jsxExprContainer = t.JSXExpressionContainer(logicalExpression);
return jsxExprContainer;
}
return jsxElem;
}
if (thirdArgIsAbsent) {
return injectChildren(jsxElem, secondArg);
} else {
jsxElem.openingElement.attributes.push(t.JSXSpreadAttribute(secondArg));
return jsxElem;
}
};
const threeArgumentsCase = (firstArg, secondArg, thirdArg) => {
const jsxElem = twoArgumentsCase(firstArg, secondArg, false);
return injectChildren(jsxElem, thirdArg);
};
module.exports = function uglyRevTransform(node, isTopLevelCall, aCssModules) {
isCssModules = aCssModules;
let result;
result = transformHyperscriptToJsx(node, isTopLevelCall);
if (t.isJSXExpressionContainer(result)) {
result = result.expression;
}
return result;
};
|