-
Notifications
You must be signed in to change notification settings - Fork 12
/
index.js
133 lines (112 loc) · 4.58 KB
/
index.js
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
/** Visitor factory for babel, converting React.createElement(...) to <jsx ...>...</jsx>
*
* What we want to handle here is this CallExpression:
*
* React.createElement(
* type: StringLiteral|Identifier|MemberExpression,
* [props: ObjectExpression|Expression],
* [...children: StringLiteral|Expression]
* )
*
* Any of those arguments might also be missing (undefined) and/or invalid. */
export default function({types: t}) {
/** Get a JSXElement from a CallExpression
* Returns null if this impossible */
function getJSXNode(node) {
if (!isReactCreateElement(node)) return null
//nameNode and propsNode may be undefined, getJSX* need to handle that
const [nameNode, propsNode, ...childNodes] = node.arguments
const name = getJSXName(nameNode)
if (name === null) return null //name is required
const props = getJSXProps(propsNode)
if (props === null) return null //no props → [], invalid → null
const children = getJSXChildren(childNodes)
if (children === null) return null //no children → [], invalid → null
// self-closing tag if no children
const selfClosing = children.length === 0
const startTag = t.jSXOpeningElement(name, props, selfClosing)
const endTag = selfClosing ? null : t.jSXClosingElement(name)
return t.jSXElement(startTag, endTag, children, selfClosing)
}
/** Get a JSXIdentifier or JSXMemberExpression from a Node of known type.
* Returns null if a unknown node type, null or undefined is passed. */
function getJSXName(node) {
if (node == null) return null
const name = getJSXIdentifier(node)
if (name !== null) return name
if (!t.isMemberExpression(node)) return null
const object = getJSXName(node.object)
const property = getJSXName(node.property)
if (object === null || property === null) return null
return t.jSXMemberExpression(object, property)
}
/** Get a array of JSX(Spread)Attribute from a props ObjectExpression.
* Handles the _extends Expression babel creates from SpreadProperty nodes.
* Returns null if a validation error occurs. */
function getJSXProps(node) {
if (node == null || isNullLikeNode(node)) return []
if (t.isCallExpression(node) && t.isIdentifier(node.callee, { name: '_extends' })) {
const props = node.arguments.map(getJSXProps)
//if calling this recursively works, flatten.
if (props.every(prop => prop !== null))
return [].concat.apply([], props)
}
if (!t.isObjectExpression(node) && t.isExpression(node))
return [t.jSXSpreadAttribute(node)]
if (!isPlainObjectExpression(node)) return null
return node.properties.map(prop => t.isObjectProperty(prop)
? t.jSXAttribute(getJSXIdentifier(prop.key), getJSXAttributeValue(prop.value))
: t.jSXSpreadAttribute(prop.argument))
}
function getJSXChild(node) {
if (t.isStringLiteral(node)) return t.jSXText(node.value)
if (isReactCreateElement(node)) return getJSXNode(node)
if (t.isExpression(node)) return t.jSXExpressionContainer(node)
return null
}
function getJSXChildren(nodes) {
const children = nodes.filter(node => !isNullLikeNode(node)).map(getJSXChild)
if (children.some(child => child == null)) return null
return children
}
function getJSXIdentifier(node) {
//TODO: JSXNamespacedName
if (t.isIdentifier(node)) return t.jSXIdentifier(node.name)
if (t.isStringLiteral(node)) return t.jSXIdentifier(node.value)
return null
}
function getJSXAttributeValue(node) {
if (t.isStringLiteral(node)) return node
if (t.isJSXElement(node)) return node
if (t.isExpression(node)) return t.jSXExpressionContainer(node)
return null
}
/** tests if a node is a CallExpression with callee “React.createElement” */
const isReactCreateElement = node =>
t.isCallExpression(node) &&
t.isMemberExpression(node.callee) &&
t.isIdentifier(node.callee.object, { name: 'React' }) &&
t.isIdentifier(node.callee.property, { name: 'createElement' }) &&
!node.callee.computed
/** Tests if a node is “null” or “undefined” */
const isNullLikeNode = node =>
t.isNullLiteral(node) ||
t.isIdentifier(node, { name: 'undefined' })
/** Tests if a node is an object expression with noncomputed, nonmethod attrs */
const isPlainObjectExpression = node =>
t.isObjectExpression(node) &&
node.properties.every(m =>
t.isSpreadProperty(m) ||
(t.isObjectProperty(m, {computed: false}) &&
getJSXIdentifier(m.key) !== null &&
getJSXAttributeValue(m.value) !== null))
return {
visitor: {
CallExpression(path) {
const node = getJSXNode(path.node)
if (node === null) return null
path.replaceWith(node)
}
}
}
}