UNPKG

6.77 kBJavaScriptView Raw
1var esutils = require('esutils')
2var groupProps = require('./lib/group-props')
3var mustUseProp = require('./lib/must-use-prop')
4
5var isInsideJsxExpression = function (t, path) {
6 if (!path.parentPath) {
7 return false
8 }
9 if (t.isJSXExpressionContainer(path.parentPath)) {
10 return true
11 }
12 return isInsideJsxExpression(t, path.parentPath)
13}
14
15module.exports = function (babel) {
16 var t = babel.types
17
18 return {
19 inherits: require('babel-plugin-syntax-jsx'),
20 visitor: {
21 JSXNamespacedName (path) {
22 throw path.buildCodeFrameError(
23 'Namespaced tags/attributes are not supported. JSX is not XML.\n' +
24 'For attributes like xlink:href, use xlinkHref instead.'
25 )
26 },
27 JSXElement: {
28 exit (path, file) {
29 // turn tag into createElement call
30 var callExpr = buildElementCall(path.get('openingElement'), file)
31 if (path.node.children.length) {
32 // add children array as 3rd arg
33 callExpr.arguments.push(t.arrayExpression(path.node.children))
34 if (callExpr.arguments.length >= 3) {
35 callExpr._prettyCall = true
36 }
37 }
38 path.replaceWith(t.inherits(callExpr, path.node))
39 }
40 },
41 'Program' (path) {
42 path.traverse({
43 'ObjectMethod|ClassMethod' (path) {
44 const params = path.get('params')
45 // do nothing if there is (h) param
46 if (params.length && params[0].node.name === 'h') {
47 return
48 }
49 // do nothing if there is no JSX inside
50 const jsxChecker = {
51 hasJsx: false
52 }
53 path.traverse({
54 JSXElement () {
55 this.hasJsx = true
56 }
57 }, jsxChecker)
58 if (!jsxChecker.hasJsx) {
59 return
60 }
61 // do nothing if this method is a part of JSX expression
62 if (isInsideJsxExpression(t, path)) {
63 return
64 }
65 const isRender = path.node.key.name === 'render'
66 // inject h otherwise
67 path.get('body').unshiftContainer('body', t.variableDeclaration('const', [
68 t.variableDeclarator(
69 t.identifier('h'),
70 (
71 isRender
72 ? t.memberExpression(
73 t.identifier('arguments'),
74 t.numericLiteral(0),
75 true
76 )
77 : t.memberExpression(
78 t.thisExpression(),
79 t.identifier('$createElement')
80 )
81 )
82 )
83 ]))
84 },
85 JSXOpeningElement (path) {
86 const tag = path.get('name').node.name
87 const attributes = path.get('attributes')
88 const typeAttribute = attributes.find(attributePath => attributePath.node.name && attributePath.node.name.name === 'type')
89 const type = typeAttribute && t.isStringLiteral(typeAttribute.node.value) ? typeAttribute.node.value.value : null
90
91 attributes.forEach(attributePath => {
92 const attribute = attributePath.get('name')
93
94 if (!attribute.node) {
95 return
96 }
97
98 const attr = attribute.node.name
99
100 if (mustUseProp(tag, type, attr) && t.isJSXExpressionContainer(attributePath.node.value)) {
101 attribute.replaceWith(t.JSXIdentifier(`domProps-${attr}`))
102 }
103 })
104 }
105 })
106 }
107 }
108 }
109
110 function buildElementCall (path, file) {
111 path.parent.children = t.react.buildChildren(path.parent)
112 var tagExpr = convertJSXIdentifier(path.node.name, path.node)
113 var args = []
114
115 var tagName
116 if (t.isIdentifier(tagExpr)) {
117 tagName = tagExpr.name
118 } else if (t.isLiteral(tagExpr)) {
119 tagName = tagExpr.value
120 }
121
122 if (t.react.isCompatTag(tagName)) {
123 args.push(t.stringLiteral(tagName))
124 } else {
125 args.push(tagExpr)
126 }
127
128 var attribs = path.node.attributes
129 if (attribs.length) {
130 attribs = buildOpeningElementAttributes(attribs, file)
131 args.push(attribs)
132 }
133 return t.callExpression(t.identifier('h'), args)
134 }
135
136 function convertJSXIdentifier (node, parent) {
137 if (t.isJSXIdentifier(node)) {
138 if (node.name === 'this' && t.isReferenced(node, parent)) {
139 return t.thisExpression()
140 } else if (esutils.keyword.isIdentifierNameES6(node.name)) {
141 node.type = 'Identifier'
142 } else {
143 return t.stringLiteral(node.name)
144 }
145 } else if (t.isJSXMemberExpression(node)) {
146 return t.memberExpression(
147 convertJSXIdentifier(node.object, node),
148 convertJSXIdentifier(node.property, node)
149 )
150 }
151 return node
152 }
153
154 /**
155 * The logic for this is quite terse. It's because we need to
156 * support spread elements. We loop over all attributes,
157 * breaking on spreads, we then push a new object containing
158 * all prior attributes to an array for later processing.
159 */
160
161 function buildOpeningElementAttributes (attribs, file) {
162 var _props = []
163 var objs = []
164
165 function pushProps () {
166 if (!_props.length) return
167 objs.push(t.objectExpression(_props))
168 _props = []
169 }
170
171 while (attribs.length) {
172 var prop = attribs.shift()
173 if (t.isJSXSpreadAttribute(prop)) {
174 pushProps()
175 prop.argument._isSpread = true
176 objs.push(prop.argument)
177 } else {
178 _props.push(convertAttribute(prop))
179 }
180 }
181
182 pushProps()
183
184 objs = objs.map(function (o) {
185 return o._isSpread ? o : groupProps(o.properties, t)
186 })
187
188 if (objs.length === 1) {
189 // only one object
190 attribs = objs[0]
191 } else if (objs.length) {
192 // add prop merging helper
193 var helper = file.addImport('babel-helper-vue-jsx-merge-props', 'default', '_mergeJSXProps')
194 // spread it
195 attribs = t.callExpression(
196 helper,
197 [t.arrayExpression(objs)]
198 )
199 }
200 return attribs
201 }
202
203 function convertAttribute (node) {
204 var value = convertAttributeValue(node.value || t.booleanLiteral(true))
205 if (t.isStringLiteral(value) && !t.isJSXExpressionContainer(node.value)) {
206 value.value = value.value.replace(/\n\s+/g, ' ')
207 }
208 if (t.isValidIdentifier(node.name.name)) {
209 node.name.type = 'Identifier'
210 } else {
211 node.name = t.stringLiteral(node.name.name)
212 }
213 return t.inherits(t.objectProperty(node.name, value), node)
214 }
215
216 function convertAttributeValue (node) {
217 if (t.isJSXExpressionContainer(node)) {
218 return node.expression
219 } else {
220 return node
221 }
222 }
223}