@@ -5,10 +5,13 @@ module.exports = buildJsx
55var walk = require ( 'estree-walker' ) . walk
66var isIdentifierName = require ( 'estree-util-is-identifier-name' ) . name
77
8+ var regex = / @ ( j s x | j s x F r a g | j s x I m p o r t S o u r c e | j s x R u n t i m e ) \s + ( \S + ) / g
9+
810function buildJsx ( tree , options ) {
911 var settings = options || { }
10- var pragma = settings . pragma
11- var pragmaFrag = settings . pragmaFrag
12+ var automatic = settings . runtime === 'automatic'
13+ var annotations = { }
14+ var imports = { }
1215
1316 walk ( tree , { enter : enter , leave : leave } )
1417
@@ -26,12 +29,36 @@ function buildJsx(tree, options) {
2629 index = - 1
2730
2831 while ( ++ index < comments . length ) {
29- if ( ( match = / @ j s x \s + ( \S + ) / . exec ( comments [ index ] . value ) ) ) {
30- pragma = match [ 1 ]
32+ regex . lastIndex = 0
33+
34+ while ( ( match = regex . exec ( comments [ index ] . value ) ) ) {
35+ annotations [ match [ 1 ] ] = match [ 2 ]
3136 }
37+ }
38+
39+ if ( annotations . jsxRuntime ) {
40+ if ( annotations . jsxRuntime === 'automatic' ) {
41+ automatic = true
42+
43+ if ( annotations . jsx ) {
44+ throw new Error ( 'Unexpected `@jsx` pragma w/ automatic runtime' )
45+ }
3246
33- if ( ( match = / @ j s x F r a g \s + ( \S + ) / . exec ( comments [ index ] . value ) ) ) {
34- pragmaFrag = match [ 1 ]
47+ if ( annotations . jsxFrag ) {
48+ throw new Error ( 'Unexpected `@jsxFrag` pragma w/ automatic runtime' )
49+ }
50+ } else if ( annotations . jsxRuntime === 'classic' ) {
51+ automatic = false
52+
53+ if ( annotations . jsxImportSource ) {
54+ throw new Error ( 'Unexpected `@jsxImportSource` w/ classic runtime' )
55+ }
56+ } else {
57+ throw new Error (
58+ 'Unexpected `jsxRuntime` `' +
59+ annotations . jsxRuntime +
60+ '`, expected `automatic` or `classic`'
61+ )
3562 }
3663 }
3764 }
@@ -41,19 +68,70 @@ function buildJsx(tree, options) {
4168 // eslint-disable-next-line complexity
4269 function leave ( node ) {
4370 var parameters
71+ var children
4472 var fields
4573 var objects
4674 var index
4775 var child
4876 var name
4977 var props
5078 var attributes
79+ var spread
80+ var key
81+ var callee
82+ var specifiers
83+ var prop
84+
85+ if ( node . type === 'Program' ) {
86+ specifiers = [ ]
87+
88+ if ( imports . fragment ) {
89+ specifiers . push ( {
90+ type : 'ImportSpecifier' ,
91+ imported : { type : 'Identifier' , name : 'Fragment' } ,
92+ local : { type : 'Identifier' , name : '_Fragment' }
93+ } )
94+ }
95+
96+ if ( imports . jsx ) {
97+ specifiers . push ( {
98+ type : 'ImportSpecifier' ,
99+ imported : { type : 'Identifier' , name : 'jsx' } ,
100+ local : { type : 'Identifier' , name : '_jsx' }
101+ } )
102+ }
103+
104+ if ( imports . jsxs ) {
105+ specifiers . push ( {
106+ type : 'ImportSpecifier' ,
107+ imported : { type : 'Identifier' , name : 'jsxs' } ,
108+ local : { type : 'Identifier' , name : '_jsxs' }
109+ } )
110+ }
111+
112+ if ( specifiers . length ) {
113+ node . body . unshift ( {
114+ type : 'ImportDeclaration' ,
115+ specifiers : specifiers ,
116+ source : {
117+ type : 'Literal' ,
118+ value :
119+ ( annotations . jsxImportSource ||
120+ settings . importSource ||
121+ 'react' ) + '/jsx-runtime'
122+ }
123+ } )
124+ }
125+ }
51126
52127 if ( node . type !== 'JSXElement' && node . type !== 'JSXFragment' ) {
53128 return
54129 }
55130
56131 parameters = [ ]
132+ children = [ ]
133+ objects = [ ]
134+ fields = [ ]
57135 index = - 1
58136
59137 // Figure out `children`.
@@ -86,22 +164,20 @@ function buildJsx(tree, options) {
86164 }
87165 // Otherwise, this is an already compiled call.
88166
89- parameters . push ( child )
167+ children . push ( child )
90168 }
91169
92170 // Do the stuff needed for elements.
93171 if ( node . openingElement ) {
94172 name = toIdentifier ( node . openingElement . name )
95173
96- // If the name could be an identifier, but start with something other than
97- // a lowercase letter, it’s not a component.
174+ // If the name could be an identifier, but start with a lowercase letter,
175+ // it’s not a component.
98176 if ( name . type === 'Identifier' && / ^ [ a - z ] / . test ( name . name ) ) {
99177 name = create ( name , { type : 'Literal' , value : name . name } )
100178 }
101179
102180 attributes = node . openingElement . attributes
103- objects = [ ]
104- fields = [ ]
105181 index = - 1
106182
107183 // Place props in the right order, because we might have duplicates
@@ -114,46 +190,100 @@ function buildJsx(tree, options) {
114190 }
115191
116192 objects . push ( attributes [ index ] . argument )
193+ spread = true
117194 } else {
118- fields . push ( toProperty ( attributes [ index ] ) )
195+ prop = toProperty ( attributes [ index ] )
196+
197+ if ( automatic && prop . key . name === 'key' ) {
198+ if ( spread ) {
199+ throw new Error (
200+ 'Expected `key` to come before any spread expressions'
201+ )
202+ }
203+
204+ key = prop . value
205+ } else {
206+ fields . push ( prop )
207+ }
119208 }
120209 }
210+ }
211+ // …and fragments.
212+ else if ( automatic ) {
213+ imports . fragment = true
214+ name = { type : 'Identifier' , name : '_Fragment' }
215+ } else {
216+ name = toMemberExpression (
217+ annotations . jsxFrag || settings . pragmaFrag || 'React.Fragment'
218+ )
219+ }
220+
221+ if ( automatic && children . length ) {
222+ fields . push ( {
223+ type : 'Property' ,
224+ key : { type : 'Identifier' , name : 'children' } ,
225+ value :
226+ children . length > 1
227+ ? { type : 'ArrayExpression' , elements : children }
228+ : children [ 0 ] ,
229+ kind : 'init'
230+ } )
231+ } else {
232+ parameters = children
233+ }
234+
235+ if ( fields . length ) {
236+ objects . push ( { type : 'ObjectExpression' , properties : fields } )
237+ }
121238
122- if ( fields . length ) {
123- objects . push ( { type : 'ObjectExpression' , properties : fields } )
239+ if ( objects . length > 1 ) {
240+ // Don’t mutate the first object, shallow clone instead.
241+ if ( objects [ 0 ] . type !== 'ObjectExpression' ) {
242+ objects . unshift ( { type : 'ObjectExpression' , properties : [ ] } )
124243 }
125244
126- if ( objects . length > 1 ) {
127- // Don’t mutate the first object, shallow clone instead.
128- if ( objects [ 0 ] . type !== 'ObjectExpression' ) {
129- objects . unshift ( { type : 'ObjectExpression' , properties : [ ] } )
130- }
245+ props = {
246+ type : 'CallExpression' ,
247+ callee : toMemberExpression ( 'Object.assign' ) ,
248+ arguments : objects
249+ }
250+ } else if ( objects . length ) {
251+ props = objects [ 0 ]
252+ }
131253
132- props = {
133- type : 'CallExpression' ,
134- callee : toMemberExpression ( 'Object.assign' ) ,
135- arguments : objects
136- }
137- } else if ( objects . length ) {
138- props = objects [ 0 ]
254+ if ( automatic ) {
255+ if ( children . length > 1 ) {
256+ imports . jsxs = true
257+ callee = { type : 'Identifier' , name : '_jsxs' }
258+ } else {
259+ imports . jsx = true
260+ callee = { type : 'Identifier' , name : '_jsx' }
261+ }
262+
263+ parameters . push ( props || { type : 'ObjectExpression' , properties : [ ] } )
264+
265+ if ( key ) {
266+ parameters . push ( key )
139267 }
140268 }
141- // …and fragments .
269+ // Classic .
142270 else {
143- name = toMemberExpression ( pragmaFrag || 'React.Fragment' )
144- }
271+ // There are props or children.
272+ if ( props || parameters . length ) {
273+ parameters . unshift ( props || { type : 'Literal' , value : null } )
274+ }
145275
146- // There are props or children.
147- if ( props || parameters . length ) {
148- parameters . unshift ( props || { type : 'Literal' , value : null } )
276+ callee = toMemberExpression (
277+ annotations . jsx || settings . pragma || 'React.createElement'
278+ )
149279 }
150280
151281 parameters . unshift ( name )
152282
153283 this . replace (
154284 create ( node , {
155285 type : 'CallExpression' ,
156- callee : toMemberExpression ( pragma || 'React.createElement' ) ,
286+ callee : callee ,
157287 arguments : parameters
158288 } )
159289 )
0 commit comments