@samitouri / QOS-React-2 / commits / f512537754

Babel Transform JSX to React.jsx/React.jsxDEV Plugin (#16432)

This babel transform is a fork of the @babel/plugin-transform-react-jsx transform and is for experimentation purposes only. We don't plan to own this code in the future, and we will upstream this to Babel at some point once we've proven out the concept. As per the RFC to simplify element creation, we want to change the JSX transform from targeting React.createElement(type, props, children) to React.jsx(type, props, key). This modifies the existing @babel/plugin-transform-react-jsx (and helper) babel plugin to support React.jsx and React.jsxDEV. The main differences between React.jsx/React.jsxDEV and React.createElement are: 1.) key is now passed as an explicit argument rather than through props 3.) children are now passed through props rather than as an explicit argument 4.) props must always be an object 5.) __source and and __self are now passed as separate arguments into React.jsxDEV rather than through props Part of the rationale for this change is that we want to deprecate key spread through props because this is an expensive dynamic comparison operation. We want users instead always explicitly pass key as a prop. However, in the interim, we need a way to distinguish between <div {...props} key={foo} /> and <div key={foo} {...props} />. Therefore, until we completely deprecate key spreading, we will use React.createElement to transform <div {...props} key="Hi" /> and React.jsx to transform everything else.

Luna Ruan committed Aug 27, 2019 at 16:00 UTC f5125377543749a4cee6ac887a006b6567767862
13 files changed +2162 -19
package.json
+1
@@ -4,6 +4,7 @@
4 "packages/*"
5 ],
6 "devDependencies": {
7 + "@babel/plugin-syntax-jsx": "^7.2.0",
8 "@babel/cli": "^7.0.0",
9 "@babel/code-frame": "^7.0.0",
10 "@babel/core": "^7.0.0",
packages/babel-plugin-react-jsx/README.md new
+5
@@ -0,0 +1,5 @@
1 +This package is intended to eventually replace the current `@babel/plugin-transform-react-jsx`, changing the JSX transform from targeting `React.createElement(type, props, children)` to `React.jsx(types, props, key)`.
2 +
3 +https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md
4 +
5 +**This is experimental and not intended to be used directly.**
packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactCreateElement-test.js new
+392
@@ -0,0 +1,392 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +/* eslint-disable quotes */
8 +'use strict';
9 +
10 +const babel = require('@babel/core');
11 +const codeFrame = require('@babel/code-frame');
12 +const {wrap} = require('jest-snapshot-serializer-raw');
13 +
14 +function transform(input, options) {
15 + return wrap(
16 + babel.transform(input, {
17 + configFile: false,
18 + plugins: [
19 + '@babel/plugin-syntax-jsx',
20 + '@babel/plugin-transform-arrow-functions',
21 + ...(options && options.development
22 + ? [
23 + '@babel/plugin-transform-react-jsx-source',
24 + '@babel/plugin-transform-react-jsx-self',
25 + ]
26 + : []),
27 + [
28 + './packages/babel-plugin-react-jsx',
29 + {
30 + development: __DEV__,
31 + useBuiltIns: true,
32 + useCreateElement: true,
33 + ...options,
34 + },
35 + ],
36 + ],
37 + }).code
38 + );
39 +}
40 +
41 +describe('transform react to jsx', () => {
42 + it('fragment with no children', () => {
43 + expect(transform(`var x = <></>`)).toMatchSnapshot();
44 + });
45 +
46 + it('React.Fragment to set keys and source', () => {
47 + expect(
48 + transform(`var x = <React.Fragment key='foo'><div /></React.Fragment>`, {
49 + development: true,
50 + })
51 + ).toMatchSnapshot();
52 + });
53 +
54 + it('normal fragments not to set key and source', () => {
55 + expect(
56 + transform(`var x = <><div /></>`, {
57 + development: true,
58 + })
59 + ).toMatchSnapshot();
60 + });
61 +
62 + it('should properly handle comments adjacent to children', () => {
63 + expect(
64 + transform(`
65 + var x = (
66 + <div>
67 + {/* A comment at the beginning */}
68 + {/* A second comment at the beginning */}
69 + <span>
70 + {/* A nested comment */}
71 + </span>
72 + {/* A sandwiched comment */}
73 + <br />
74 + {/* A comment at the end */}
75 + {/* A second comment at the end */}
76 + </div>
77 + );
78 + `)
79 + ).toMatchSnapshot();
80 + });
81 +
82 + it('adds appropriate new lines when using spread attribute', () => {
83 + expect(transform(`<Component {...props} sound="moo" />`)).toMatchSnapshot();
84 + });
85 +
86 + it('arrow functions', () => {
87 + expect(
88 + transform(`
89 + var foo = function () {
90 + return () => <this />;
91 + };
92 +
93 + var bar = function () {
94 + return () => <this.foo />;
95 + };
96 +
97 + `)
98 + ).toMatchSnapshot();
99 + });
100 +
101 + it('assignment', () => {
102 + expect(
103 + transform(`var div = <Component {...props} foo="bar" />`)
104 + ).toMatchSnapshot();
105 + });
106 +
107 + it('concatenates adjacent string literals', () => {
108 + expect(
109 + transform(`
110 + var x =
111 + <div>
112 + foo
113 + {"bar"}
114 + baz
115 + <div>
116 + buz
117 + bang
118 + </div>
119 + qux
120 + {null}
121 + quack
122 + </div>
123 + `)
124 + ).toMatchSnapshot();
125 + });
126 +
127 + it('should allow constructor as prop', () => {
128 + expect(transform(`<Component constructor="foo" />;`)).toMatchSnapshot();
129 + });
130 +
131 + it('should allow deeper js namespacing', () => {
132 + expect(
133 + transform(`<Namespace.DeepNamespace.Component />;`)
134 + ).toMatchSnapshot();
135 + });
136 +
137 + it('should allow elements as attributes', () => {
138 + expect(transform(`<div attr=<div /> />`)).toMatchSnapshot();
139 + });
140 +
141 + it('should allow js namespacing', () => {
142 + expect(transform(`<Namespace.Component />;`)).toMatchSnapshot();
143 + });
144 +
145 + it('should allow nested fragments', () => {
146 + expect(
147 + transform(`
148 + <div>
149 + < >
150 + <>
151 + <span>Hello</span>
152 + <span>world</span>
153 + </>
154 + <>
155 + <span>Goodbye</span>
156 + <span>world</span>
157 + </>
158 + </>
159 + </div>
160 + `)
161 + ).toMatchSnapshot();
162 + });
163 +
164 + it('should avoid wrapping in extra parens if not needed', () => {
165 + expect(
166 + transform(`
167 + var x = <div>
168 + <Component />
169 + </div>;
170 +
171 + var x = <div>
172 + {props.children}
173 + </div>;
174 +
175 + var x = <Composite>
176 + {props.children}
177 + </Composite>;
178 +
179 + var x = <Composite>
180 + <Composite2 />
181 + </Composite>;
182 + `)
183 + ).toMatchSnapshot();
184 + });
185 +
186 + it('should convert simple tags', () => {
187 + expect(transform(`var x = <div></div>;`)).toMatchSnapshot();
188 + });
189 +
190 + it('should convert simple text', () => {
191 + expect(transform(`var x = <div>text</div>;`)).toMatchSnapshot();
192 + });
193 +
194 + it('should disallow spread children', () => {
195 + let _error;
196 + const code = `<div>{...children}</div>;`;
197 + try {
198 + transform(code);
199 + } catch (error) {
200 + _error = error;
201 + }
202 + expect(_error).toEqual(
203 + new SyntaxError(
204 + 'undefined: Spread children are not supported in React.' +
205 + '\n' +
206 + codeFrame.codeFrameColumns(
207 + code,
208 + {start: {line: 1, column: 6}},
209 + {highlightCode: true}
210 + )
211 + )
212 + );
213 + });
214 +
215 + it('should escape xhtml jsxattribute', () => {
216 + expect(
217 + transform(`
218 + <div id="wôw" />;
219 + <div id="\w" />;
220 + <div id="w &lt; w" />;
221 + `)
222 + ).toMatchSnapshot();
223 + });
224 +
225 + it('should escape xhtml jsxtext', () => {
226 + /* eslint-disable no-irregular-whitespace */
227 + expect(
228 + transform(`
229 + <div>wow</div>;
230 + <div>wôw</div>;
231 +
232 + <div>w & w</div>;
233 + <div>w &amp; w</div>;
234 +
235 + <div>w &nbsp; w</div>;
236 + <div>this should not parse as unicode: \u00a0</div>;
237 + <div>this should parse as nbsp:   </div>;
238 + <div>this should parse as unicode: {'\u00a0 '}</div>;
239 +
240 + <div>w &lt; w</div>;
241 + `)
242 + ).toMatchSnapshot();
243 + /*eslint-enable */
244 + });
245 +
246 + it('should handle attributed elements', () => {
247 + expect(
248 + transform(`
249 + var HelloMessage = React.createClass({
250 + render: function() {
251 + return <div>Hello {this.props.name}</div>;
252 + }
253 + });
254 +
255 + React.render(<HelloMessage name={
256 + <span>
257 + Sebastian
258 + </span>
259 + } />, mountNode);
260 + `)
261 + ).toMatchSnapshot();
262 + });
263 +
264 + it('should handle has own property correctly', () => {
265 + expect(
266 + transform(`<hasOwnProperty>testing</hasOwnProperty>;`)
267 + ).toMatchSnapshot();
268 + });
269 +
270 + it('should have correct comma in nested children', () => {
271 + expect(
272 + transform(`
273 + var x = <div>
274 + <div><br /></div>
275 + <Component>{foo}<br />{bar}</Component>
276 + <br />
277 + </div>;
278 + `)
279 + ).toMatchSnapshot();
280 + });
281 +
282 + it('should insert commas after expressions before whitespace', () => {
283 + expect(
284 + transform(`
285 + var x =
286 + <div
287 + attr1={
288 + "foo" + "bar"
289 + }
290 + attr2={
291 + "foo" + "bar" +
292 +
293 + "baz" + "bug"
294 + }
295 + attr3={
296 + "foo" + "bar" +
297 + "baz" + "bug"
298 + }
299 + attr4="baz">
300 + </div>
301 + `)
302 + ).toMatchSnapshot();
303 + });
304 +
305 + it('should not add quotes to identifier names', () => {
306 + expect(
307 + transform(`var e = <F aaa new const var default foo-bar/>;`)
308 + ).toMatchSnapshot();
309 + });
310 +
311 + it('should not strip nbsp even couple with other whitespace', () => {
312 + expect(transform(`<div>&nbsp; </div>;`)).toMatchSnapshot();
313 + });
314 +
315 + it('should not strip tags with a single child of nbsp', () => {
316 + expect(transform(`<div>&nbsp;</div>;`)).toMatchSnapshot();
317 + });
318 +
319 + it('should properly handle comments between props', () => {
320 + expect(
321 + transform(`
322 + var x = (
323 + <div
324 + /* a multi-line
325 + comment */
326 + attr1="foo">
327 + <span // a double-slash comment
328 + attr2="bar"
329 + />
330 + </div>
331 + );
332 + `)
333 + ).toMatchSnapshot();
334 + });
335 +
336 + it('should quote jsx attributes', () => {
337 + expect(
338 + transform(`<button data-value='a value'>Button</button>`)
339 + ).toMatchSnapshot();
340 + });
341 +
342 + it('should support xml namespaces if flag', () => {
343 + expect(
344 + transform('<f:image n:attr />', {throwIfNamespace: false})
345 + ).toMatchSnapshot();
346 + });
347 +
348 + it('should throw error namespaces if not flag', () => {
349 + let _error;
350 + const code = `<f:image />`;
351 + try {
352 + transform(code);
353 + } catch (error) {
354 + _error = error;
355 + }
356 + expect(_error).toEqual(
357 + new SyntaxError(
358 + "undefined: Namespace tags are not supported by default. React's " +
359 + "JSX doesn't support namespace tags. You can turn on the " +
360 + "'throwIfNamespace' flag to bypass this warning." +
361 + '\n' +
362 + codeFrame.codeFrameColumns(
363 + code,
364 + {start: {line: 1, column: 2}},
365 + {highlightCode: true}
366 + )
367 + )
368 + );
369 + });
370 +
371 + it('should transform known hyphenated tags', () => {
372 + expect(transform(`<font-face />`)).toMatchSnapshot();
373 + });
374 +
375 + it('wraps props in react spread for first spread attributes', () => {
376 + expect(transform(`<Component {...x} y={2} z />`)).toMatchSnapshot();
377 + });
378 +
379 + it('wraps props in react spread for last spread attributes', () => {
380 + expect(transform(`<Component y={2} z { ... x } />`)).toMatchSnapshot();
381 + });
382 +
383 + it('wraps props in react spread for middle spread attributes', () => {
384 + expect(transform(`<Component y={2} { ... x } z />`)).toMatchSnapshot();
385 + });
386 +
387 + it('useBuiltIns false uses extend instead of Object.assign', () => {
388 + expect(
389 + transform(`<Component y={2} {...x} />`, {useBuiltIns: false})
390 + ).toMatchSnapshot();
391 + });
392 +});
packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js new
+483
@@ -0,0 +1,483 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +/* eslint-disable quotes */
8 +'use strict';
9 +
10 +const babel = require('@babel/core');
11 +const codeFrame = require('@babel/code-frame');
12 +const {wrap} = require('jest-snapshot-serializer-raw');
13 +
14 +function transform(input, options) {
15 + return wrap(
16 + babel.transform(input, {
17 + configFile: false,
18 + plugins: [
19 + '@babel/plugin-syntax-jsx',
20 + '@babel/plugin-transform-arrow-functions',
21 + ...(options && options.development
22 + ? [
23 + '@babel/plugin-transform-react-jsx-source',
24 + '@babel/plugin-transform-react-jsx-self',
25 + ]
26 + : []),
27 + [
28 + './packages/babel-plugin-react-jsx',
29 + {
30 + useBuiltIns: true,
31 + useCreateElement: false,
32 + ...options,
33 + },
34 + ],
35 + ],
36 + }).code
37 + );
38 +}
39 +
40 +describe('transform react to jsx', () => {
41 + it('fragment with no children', () => {
42 + expect(transform(`var x = <></>`)).toMatchSnapshot();
43 + });
44 +
45 + it('fragments', () => {
46 + expect(transform(`var x = <><div /></>`)).toMatchSnapshot();
47 + });
48 +
49 + it('fragments to set keys', () => {
50 + expect(
51 + transform(`var x = <React.Fragment key="foo"></React.Fragment>`)
52 + ).toMatchSnapshot();
53 + });
54 +
55 + it('React.fragment to set keys and source', () => {
56 + expect(
57 + transform(`var x = <React.Fragment key='foo'></React.Fragment>`, {
58 + development: true,
59 + })
60 + ).toMatchSnapshot();
61 + });
62 +
63 + it('fragments in dev mode (no key and source)', () => {
64 + expect(
65 + transform(`var x = <><div /></>`, {
66 + development: true,
67 + })
68 + ).toMatchSnapshot();
69 + });
70 +
71 + it('nonStatic children', () => {
72 + expect(
73 + transform(
74 + `var x = (
75 + <div>
76 + {[<span key={'0'} />, <span key={'1'} />]}
77 + </div>
78 + );
79 + `,
80 + {
81 + development: true,
82 + }
83 + )
84 + ).toMatchSnapshot();
85 + });
86 +
87 + it('static children', () => {
88 + expect(
89 + transform(
90 + `var x = (
91 + <div>
92 + <span />
93 + {[<span key={'0'} />, <span key={'1'} />]}
94 + </div>
95 + );
96 + `,
97 + {
98 + development: true,
99 + }
100 + )
101 + ).toMatchSnapshot();
102 + });
103 +
104 + it('uses jsxDEV instead of jsx in dev mode', () => {
105 + expect(
106 + transform(`var x = <span propOne="one">Hi</span>`, {development: true})
107 + ).toMatchSnapshot();
108 + });
109 +
110 + it('properly passes in source and self', () => {
111 + expect(
112 + transform(`var x = <div />;`, {development: true})
113 + ).toMatchSnapshot();
114 + });
115 +
116 + it('should properly handle potentially null variables', () => {
117 + expect(
118 + transform(`
119 + var foo = null;
120 + var x = <div {...foo} />;
121 + `)
122 + ).toMatchSnapshot();
123 + });
124 +
125 + it('properly handles keys', () => {
126 + expect(
127 + transform(`var x = (
128 + <div>
129 + <div key="1" />
130 + <div key="2" meow="wolf" />
131 + <div key="3" />
132 + </div>
133 + );`)
134 + ).toMatchSnapshot();
135 + });
136 +
137 + it('uses createElement when the key comes after a spread', () => {
138 + expect(
139 + transform(`var x = (
140 + <div {...props} key="1" foo="bar" />
141 + );`)
142 + ).toMatchSnapshot();
143 + });
144 +
145 + it('uses jsx when the key comes before a spread', () => {
146 + expect(
147 + transform(`var x = (
148 + <div key="1" {...props} foo="bar" />
149 + );`)
150 + ).toMatchSnapshot();
151 + });
152 +
153 + it('should properly handle comments adjacent to children', () => {
154 + expect(
155 + transform(`
156 + var x = (
157 + <div>
158 + {/* A comment at the beginning */}
159 + {/* A second comment at the beginning */}
160 + <span>
161 + {/* A nested comment */}
162 + </span>
163 + {/* A sandwiched comment */}
164 + <br />
165 + {/* A comment at the end */}
166 + {/* A second comment at the end */}
167 + </div>
168 + );
169 + `)
170 + ).toMatchSnapshot();
171 + });
172 +
173 + it('adds appropriate new lines when using spread attribute', () => {
174 + expect(transform(`<Component {...props} sound="moo" />`)).toMatchSnapshot();
175 + });
176 +
177 + it('arrow functions', () => {
178 + expect(
179 + transform(`
180 + var foo = function () {
181 + return () => <this />;
182 + };
183 +
184 + var bar = function () {
185 + return () => <this.foo />;
186 + };
187 +
188 + `)
189 + ).toMatchSnapshot();
190 + });
191 +
192 + it('assignment', () => {
193 + expect(
194 + transform(`var div = <Component {...props} foo="bar" />`)
195 + ).toMatchSnapshot();
196 + });
197 +
198 + it('concatenates adjacent string literals', () => {
199 + expect(
200 + transform(`
201 + var x =
202 + <div>
203 + foo
204 + {"bar"}
205 + baz
206 + <div>
207 + buz
208 + bang
209 + </div>
210 + qux
211 + {null}
212 + quack
213 + </div>
214 + `)
215 + ).toMatchSnapshot();
216 + });
217 +
218 + it('should allow constructor as prop', () => {
219 + expect(transform(`<Component constructor="foo" />;`)).toMatchSnapshot();
220 + });
221 +
222 + it('should allow deeper js namespacing', () => {
223 + expect(
224 + transform(`<Namespace.DeepNamespace.Component />;`)
225 + ).toMatchSnapshot();
226 + });
227 +
228 + it('should allow elements as attributes', () => {
229 + expect(transform(`<div attr=<div /> />`)).toMatchSnapshot();
230 + });
231 +
232 + it('should allow js namespacing', () => {
233 + expect(transform(`<Namespace.Component />;`)).toMatchSnapshot();
234 + });
235 +
236 + it('should allow nested fragments', () => {
237 + expect(
238 + transform(`
239 + <div>
240 + < >
241 + <>
242 + <span>Hello</span>
243 + <span>world</span>
244 + </>
245 + <>
246 + <span>Goodbye</span>
247 + <span>world</span>
248 + </>
249 + </>
250 + </div>
251 + `)
252 + ).toMatchSnapshot();
253 + });
254 +
255 + it('should avoid wrapping in extra parens if not needed', () => {
256 + expect(
257 + transform(`
258 + var x = <div>
259 + <Component />
260 + </div>;
261 +
262 + var x = <div>
263 + {props.children}
264 + </div>;
265 +
266 + var x = <Composite>
267 + {props.children}
268 + </Composite>;
269 +
270 + var x = <Composite>
271 + <Composite2 />
272 + </Composite>;
273 + `)
274 + ).toMatchSnapshot();
275 + });
276 +
277 + it('should convert simple tags', () => {
278 + expect(transform(`var x = <div></div>;`)).toMatchSnapshot();
279 + });
280 +
281 + it('should convert simple text', () => {
282 + expect(transform(`var x = <div>text</div>;`)).toMatchSnapshot();
283 + });
284 +
285 + it('should disallow spread children', () => {
286 + let _error;
287 + const code = `<div>{...children}</div>;`;
288 + try {
289 + transform(code);
290 + } catch (error) {
291 + _error = error;
292 + }
293 + expect(_error).toEqual(
294 + new SyntaxError(
295 + 'undefined: Spread children are not supported in React.' +
296 + '\n' +
297 + codeFrame.codeFrameColumns(
298 + code,
299 + {start: {line: 1, column: 6}},
300 + {highlightCode: true}
301 + )
302 + )
303 + );
304 + });
305 +
306 + it('should escape xhtml jsxattribute', () => {
307 + expect(
308 + transform(`
309 + <div id="wôw" />;
310 + <div id="\w" />;
311 + <div id="w &lt; w" />;
312 + `)
313 + ).toMatchSnapshot();
314 + });
315 +
316 + it('should escape xhtml jsxtext', () => {
317 + /* eslint-disable no-irregular-whitespace */
318 + expect(
319 + transform(`
320 + <div>wow</div>;
321 + <div>wôw</div>;
322 +
323 + <div>w & w</div>;
324 + <div>w &amp; w</div>;
325 +
326 + <div>w &nbsp; w</div>;
327 + <div>this should not parse as unicode: \u00a0</div>;
328 + <div>this should parse as nbsp:   </div>;
329 + <div>this should parse as unicode: {'\u00a0 '}</div>;
330 +
331 + <div>w &lt; w</div>;
332 + `)
333 + ).toMatchSnapshot();
334 + /*eslint-enable */
335 + });
336 +
337 + it('should handle attributed elements', () => {
338 + expect(
339 + transform(`
340 + var HelloMessage = React.createClass({
341 + render: function() {
342 + return <div>Hello {this.props.name}</div>;
343 + }
344 + });
345 +
346 + React.render(<HelloMessage name={
347 + <span>
348 + Sebastian
349 + </span>
350 + } />, mountNode);
351 + `)
352 + ).toMatchSnapshot();
353 + });
354 +
355 + it('should handle has own property correctly', () => {
356 + expect(
357 + transform(`<hasOwnProperty>testing</hasOwnProperty>;`)
358 + ).toMatchSnapshot();
359 + });
360 +
361 + it('should have correct comma in nested children', () => {
362 + expect(
363 + transform(`
364 + var x = <div>
365 + <div><br /></div>
366 + <Component>{foo}<br />{bar}</Component>
367 + <br />
368 + </div>;
369 + `)
370 + ).toMatchSnapshot();
371 + });
372 +
373 + it('should insert commas after expressions before whitespace', () => {
374 + expect(
375 + transform(`
376 + var x =
377 + <div
378 + attr1={
379 + "foo" + "bar"
380 + }
381 + attr2={
382 + "foo" + "bar" +
383 +
384 + "baz" + "bug"
385 + }
386 + attr3={
387 + "foo" + "bar" +
388 + "baz" + "bug"
389 + }
390 + attr4="baz">
391 + </div>
392 + `)
393 + ).toMatchSnapshot();
394 + });
395 +
396 + it('should not add quotes to identifier names', () => {
397 + expect(
398 + transform(`var e = <F aaa new const var default foo-bar/>;`)
399 + ).toMatchSnapshot();
400 + });
401 +
402 + it('should not strip nbsp even couple with other whitespace', () => {
403 + expect(transform(`<div>&nbsp; </div>;`)).toMatchSnapshot();
404 + });
405 +
406 + it('should not strip tags with a single child of nbsp', () => {
407 + expect(transform(`<div>&nbsp;</div>;`)).toMatchSnapshot();
408 + });
409 +
410 + it('should properly handle comments between props', () => {
411 + expect(
412 + transform(`
413 + var x = (
414 + <div
415 + /* a multi-line
416 + comment */
417 + attr1="foo">
418 + <span // a double-slash comment
419 + attr2="bar"
420 + />
421 + </div>
422 + );
423 + `)
424 + ).toMatchSnapshot();
425 + });
426 +
427 + it('should quote jsx attributes', () => {
428 + expect(
429 + transform(`<button data-value='a value'>Button</button>`)
430 + ).toMatchSnapshot();
431 + });
432 +
433 + it('should support xml namespaces if flag', () => {
434 + expect(
435 + transform('<f:image n:attr />', {throwIfNamespace: false})
436 + ).toMatchSnapshot();
437 + });
438 +
439 + it('should throw error namespaces if not flag', () => {
440 + let _error;
441 + const code = `<f:image />`;
442 + try {
443 + transform(code);
444 + } catch (error) {
445 + _error = error;
446 + }
447 + expect(_error).toEqual(
448 + new SyntaxError(
449 + "undefined: Namespace tags are not supported by default. React's " +
450 + "JSX doesn't support namespace tags. You can turn on the " +
451 + "'throwIfNamespace' flag to bypass this warning." +
452 + '\n' +
453 + codeFrame.codeFrameColumns(
454 + code,
455 + {start: {line: 1, column: 2}},
456 + {highlightCode: true}
457 + )
458 + )
459 + );
460 + });
461 +
462 + it('should transform known hyphenated tags', () => {
463 + expect(transform(`<font-face />`)).toMatchSnapshot();
464 + });
465 +
466 + it('wraps props in react spread for first spread attributes', () => {
467 + expect(transform(`<Component {...x} y={2} z />`)).toMatchSnapshot();
468 + });
469 +
470 + it('wraps props in react spread for last spread attributes', () => {
471 + expect(transform(`<Component y={2} z { ... x } />`)).toMatchSnapshot();
472 + });
473 +
474 + it('wraps props in react spread for middle spread attributes', () => {
475 + expect(transform(`<Component y={2} { ... x } z />`)).toMatchSnapshot();
476 + });
477 +
478 + it('useBuiltIns false uses extend instead of Object.assign', () => {
479 + expect(
480 + transform(`<Component y={2} {...x} />`, {useBuiltIns: false})
481 + ).toMatchSnapshot();
482 + });
483 +});
packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactCreateElement-test.js.snap new
+213
@@ -0,0 +1,213 @@
1 +// Jest Snapshot v1, https://goo.gl/fbAQLP
2 +
3 +exports[`transform react to jsx React.Fragment to set keys and source 1`] = `
4 +var _jsxFileName = "";
5 +var x = React.createElement(React.Fragment, {
6 + key: "foo",
7 + __source: {
8 + fileName: _jsxFileName,
9 + lineNumber: 1
10 + },
11 + __self: this
12 +}, React.createElement("div", {
13 + __source: {
14 + fileName: _jsxFileName,
15 + lineNumber: 1
16 + },
17 + __self: this
18 +}));
19 +`;
20 +
21 +exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = `
22 +React.createElement(Component, Object.assign({}, props, {
23 + sound: "moo"
24 +}));
25 +`;
26 +
27 +exports[`transform react to jsx arrow functions 1`] = `
28 +var foo = function () {
29 + var _this = this;
30 +
31 + return function () {
32 + return React.createElement(_this, null);
33 + };
34 +};
35 +
36 +var bar = function () {
37 + var _this2 = this;
38 +
39 + return function () {
40 + return React.createElement(_this2.foo, null);
41 + };
42 +};
43 +`;
44 +
45 +exports[`transform react to jsx assignment 1`] = `
46 +var div = React.createElement(Component, Object.assign({}, props, {
47 + foo: "bar"
48 +}));
49 +`;
50 +
51 +exports[`transform react to jsx concatenates adjacent string literals 1`] = `var x = React.createElement("div", null, "foo", "bar", "baz", React.createElement("div", null, "buz bang"), "qux", null, "quack");`;
52 +
53 +exports[`transform react to jsx fragment with no children 1`] = `var x = React.createElement(React.Fragment, null);`;
54 +
55 +exports[`transform react to jsx normal fragments not to set key and source 1`] = `
56 +var _jsxFileName = "";
57 +var x = React.createElement(React.Fragment, null, React.createElement("div", {
58 + __source: {
59 + fileName: _jsxFileName,
60 + lineNumber: 1
61 + },
62 + __self: this
63 +}));
64 +`;
65 +
66 +exports[`transform react to jsx should allow constructor as prop 1`] = `
67 +React.createElement(Component, {
68 + constructor: "foo"
69 +});
70 +`;
71 +
72 +exports[`transform react to jsx should allow deeper js namespacing 1`] = `React.createElement(Namespace.DeepNamespace.Component, null);`;
73 +
74 +exports[`transform react to jsx should allow elements as attributes 1`] = `
75 +React.createElement("div", {
76 + attr: React.createElement("div", null)
77 +});
78 +`;
79 +
80 +exports[`transform react to jsx should allow js namespacing 1`] = `React.createElement(Namespace.Component, null);`;
81 +
82 +exports[`transform react to jsx should allow nested fragments 1`] = `React.createElement("div", null, React.createElement(React.Fragment, null, React.createElement(React.Fragment, null, React.createElement("span", null, "Hello"), React.createElement("span", null, "world")), React.createElement(React.Fragment, null, React.createElement("span", null, "Goodbye"), React.createElement("span", null, "world"))));`;
83 +
84 +exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = `
85 +var x = React.createElement("div", null, React.createElement(Component, null));
86 +var x = React.createElement("div", null, props.children);
87 +var x = React.createElement(Composite, null, props.children);
88 +var x = React.createElement(Composite, null, React.createElement(Composite2, null));
89 +`;
90 +
91 +exports[`transform react to jsx should convert simple tags 1`] = `var x = React.createElement("div", null);`;
92 +
93 +exports[`transform react to jsx should convert simple text 1`] = `var x = React.createElement("div", null, "text");`;
94 +
95 +exports[`transform react to jsx should escape xhtml jsxattribute 1`] = `
96 +React.createElement("div", {
97 + id: "w\\xF4w"
98 +});
99 +React.createElement("div", {
100 + id: "w"
101 +});
102 +React.createElement("div", {
103 + id: "w < w"
104 +});
105 +`;
106 +
107 +exports[`transform react to jsx should escape xhtml jsxtext 1`] = `
108 +React.createElement("div", null, "wow");
109 +React.createElement("div", null, "w\\xF4w");
110 +React.createElement("div", null, "w & w");
111 +React.createElement("div", null, "w & w");
112 +React.createElement("div", null, "w \\xA0 w");
113 +React.createElement("div", null, "this should not parse as unicode: \\xA0");
114 +React.createElement("div", null, "this should parse as nbsp: \\xA0 ");
115 +React.createElement("div", null, "this should parse as unicode: ", '  ');
116 +React.createElement("div", null, "w < w");
117 +`;
118 +
119 +exports[`transform react to jsx should handle attributed elements 1`] = `
120 +var HelloMessage = React.createClass({
121 + render: function () {
122 + return React.createElement("div", null, "Hello ", this.props.name);
123 + }
124 +});
125 +React.render(React.createElement(HelloMessage, {
126 + name: React.createElement("span", null, "Sebastian")
127 +}), mountNode);
128 +`;
129 +
130 +exports[`transform react to jsx should handle has own property correctly 1`] = `React.createElement("hasOwnProperty", null, "testing");`;
131 +
132 +exports[`transform react to jsx should have correct comma in nested children 1`] = `var x = React.createElement("div", null, React.createElement("div", null, React.createElement("br", null)), React.createElement(Component, null, foo, React.createElement("br", null), bar), React.createElement("br", null));`;
133 +
134 +exports[`transform react to jsx should insert commas after expressions before whitespace 1`] = `
135 +var x = React.createElement("div", {
136 + attr1: "foo" + "bar",
137 + attr2: "foo" + "bar" + "baz" + "bug",
138 + attr3: "foo" + "bar" + "baz" + "bug",
139 + attr4: "baz"
140 +});
141 +`;
142 +
143 +exports[`transform react to jsx should not add quotes to identifier names 1`] = `
144 +var e = React.createElement(F, {
145 + aaa: true,
146 + new: true,
147 + const: true,
148 + var: true,
149 + default: true,
150 + "foo-bar": true
151 +});
152 +`;
153 +
154 +exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = `React.createElement("div", null, "\\xA0 ");`;
155 +
156 +exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = `React.createElement("div", null, "\\xA0");`;
157 +
158 +exports[`transform react to jsx should properly handle comments adjacent to children 1`] = `var x = React.createElement("div", null, React.createElement("span", null), React.createElement("br", null));`;
159 +
160 +exports[`transform react to jsx should properly handle comments between props 1`] = `
161 +var x = React.createElement("div", {
162 + /* a multi-line
163 + comment */
164 + attr1: "foo"
165 +}, React.createElement("span", {
166 + // a double-slash comment
167 + attr2: "bar"
168 +}));
169 +`;
170 +
171 +exports[`transform react to jsx should quote jsx attributes 1`] = `
172 +React.createElement("button", {
173 + "data-value": "a value"
174 +}, "Button");
175 +`;
176 +
177 +exports[`transform react to jsx should support xml namespaces if flag 1`] = `
178 +React.createElement("f:image", {
179 + "n:attr": true
180 +});
181 +`;
182 +
183 +exports[`transform react to jsx should transform known hyphenated tags 1`] = `React.createElement("font-face", null);`;
184 +
185 +exports[`transform react to jsx useBuiltIns false uses extend instead of Object.assign 1`] = `
186 +function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
187 +
188 +React.createElement(Component, _extends({
189 + y: 2
190 +}, x));
191 +`;
192 +
193 +exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = `
194 +React.createElement(Component, Object.assign({}, x, {
195 + y: 2,
196 + z: true
197 +}));
198 +`;
199 +
200 +exports[`transform react to jsx wraps props in react spread for last spread attributes 1`] = `
201 +React.createElement(Component, Object.assign({
202 + y: 2,
203 + z: true
204 +}, x));
205 +`;
206 +
207 +exports[`transform react to jsx wraps props in react spread for middle spread attributes 1`] = `
208 +React.createElement(Component, Object.assign({
209 + y: 2
210 +}, x, {
211 + z: true
212 +}));
213 +`;
packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactJSX-test.js.snap new
+374
@@ -0,0 +1,374 @@
1 +// Jest Snapshot v1, https://goo.gl/fbAQLP
2 +
3 +exports[`transform react to jsx React.fragment to set keys and source 1`] = `
4 +var _jsxFileName = "";
5 +var x = React.jsxDEV(React.Fragment, {}, "foo", false, {
6 + fileName: _jsxFileName,
7 + lineNumber: 1
8 +}, this);
9 +`;
10 +
11 +exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = `
12 +React.jsx(Component, Object.assign({}, props, {
13 + sound: "moo"
14 +}));
15 +`;
16 +
17 +exports[`transform react to jsx arrow functions 1`] = `
18 +var foo = function () {
19 + var _this = this;
20 +
21 + return function () {
22 + return React.jsx(_this, {});
23 + };
24 +};
25 +
26 +var bar = function () {
27 + var _this2 = this;
28 +
29 + return function () {
30 + return React.jsx(_this2.foo, {});
31 + };
32 +};
33 +`;
34 +
35 +exports[`transform react to jsx assignment 1`] = `
36 +var div = React.jsx(Component, Object.assign({}, props, {
37 + foo: "bar"
38 +}));
39 +`;
40 +
41 +exports[`transform react to jsx concatenates adjacent string literals 1`] = `
42 +var x = React.jsxs("div", {
43 + children: ["foo", "bar", "baz", React.jsx("div", {
44 + children: "buz bang"
45 + }), "qux", null, "quack"]
46 +});
47 +`;
48 +
49 +exports[`transform react to jsx fragment with no children 1`] = `var x = React.jsx(React.Fragment, {});`;
50 +
51 +exports[`transform react to jsx fragments 1`] = `
52 +var x = React.jsx(React.Fragment, {
53 + children: React.jsx("div", {})
54 +});
55 +`;
56 +
57 +exports[`transform react to jsx fragments in dev mode (no key and source) 1`] = `
58 +var _jsxFileName = "";
59 +var x = React.jsxDEV(React.Fragment, {
60 + children: React.jsxDEV("div", {}, undefined, false, {
61 + fileName: _jsxFileName,
62 + lineNumber: 1
63 + }, this)
64 +}, undefined, false);
65 +`;
66 +
67 +exports[`transform react to jsx fragments to set keys 1`] = `var x = React.jsx(React.Fragment, {}, "foo");`;
68 +
69 +exports[`transform react to jsx nonStatic children 1`] = `
70 +var _jsxFileName = "";
71 +var x = React.jsxDEV("div", {
72 + children: [React.jsxDEV("span", {}, '0', false, {
73 + fileName: _jsxFileName,
74 + lineNumber: 3
75 + }, this), React.jsxDEV("span", {}, '1', false, {
76 + fileName: _jsxFileName,
77 + lineNumber: 3
78 + }, this)]
79 +}, undefined, false, {
80 + fileName: _jsxFileName,
81 + lineNumber: 2
82 +}, this);
83 +`;
84 +
85 +exports[`transform react to jsx properly handles keys 1`] = `
86 +var x = React.jsxs("div", {
87 + children: [React.jsx("div", {}, "1"), React.jsx("div", {
88 + meow: "wolf"
89 + }, "2"), React.jsx("div", {}, "3")]
90 +});
91 +`;
92 +
93 +exports[`transform react to jsx properly passes in source and self 1`] = `
94 +var _jsxFileName = "";
95 +var x = React.jsxDEV("div", {}, undefined, false, {
96 + fileName: _jsxFileName,
97 + lineNumber: 1
98 +}, this);
99 +`;
100 +
101 +exports[`transform react to jsx should allow constructor as prop 1`] = `
102 +React.jsx(Component, {
103 + constructor: "foo"
104 +});
105 +`;
106 +
107 +exports[`transform react to jsx should allow deeper js namespacing 1`] = `React.jsx(Namespace.DeepNamespace.Component, {});`;
108 +
109 +exports[`transform react to jsx should allow elements as attributes 1`] = `
110 +React.jsx("div", {
111 + attr: React.jsx("div", {})
112 +});
113 +`;
114 +
115 +exports[`transform react to jsx should allow js namespacing 1`] = `React.jsx(Namespace.Component, {});`;
116 +
117 +exports[`transform react to jsx should allow nested fragments 1`] = `
118 +React.jsx("div", {
119 + children: React.jsxs(React.Fragment, {
120 + children: [React.jsxs(React.Fragment, {
121 + children: [React.jsx("span", {
122 + children: "Hello"
123 + }), React.jsx("span", {
124 + children: "world"
125 + })]
126 + }), React.jsxs(React.Fragment, {
127 + children: [React.jsx("span", {
128 + children: "Goodbye"
129 + }), React.jsx("span", {
130 + children: "world"
131 + })]
132 + })]
133 + })
134 +});
135 +`;
136 +
137 +exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = `
138 +var x = React.jsx("div", {
139 + children: React.jsx(Component, {})
140 +});
141 +var x = React.jsx("div", {
142 + children: props.children
143 +});
144 +var x = React.jsx(Composite, {
145 + children: props.children
146 +});
147 +var x = React.jsx(Composite, {
148 + children: React.jsx(Composite2, {})
149 +});
150 +`;
151 +
152 +exports[`transform react to jsx should convert simple tags 1`] = `var x = React.jsx("div", {});`;
153 +
154 +exports[`transform react to jsx should convert simple text 1`] = `
155 +var x = React.jsx("div", {
156 + children: "text"
157 +});
158 +`;
159 +
160 +exports[`transform react to jsx should escape xhtml jsxattribute 1`] = `
161 +React.jsx("div", {
162 + id: "w\\xF4w"
163 +});
164 +React.jsx("div", {
165 + id: "w"
166 +});
167 +React.jsx("div", {
168 + id: "w < w"
169 +});
170 +`;
171 +
172 +exports[`transform react to jsx should escape xhtml jsxtext 1`] = `
173 +React.jsx("div", {
174 + children: "wow"
175 +});
176 +React.jsx("div", {
177 + children: "w\\xF4w"
178 +});
179 +React.jsx("div", {
180 + children: "w & w"
181 +});
182 +React.jsx("div", {
183 + children: "w & w"
184 +});
185 +React.jsx("div", {
186 + children: "w \\xA0 w"
187 +});
188 +React.jsx("div", {
189 + children: "this should not parse as unicode: \\xA0"
190 +});
191 +React.jsx("div", {
192 + children: "this should parse as nbsp: \\xA0 "
193 +});
194 +React.jsxs("div", {
195 + children: ["this should parse as unicode: ", '  ']
196 +});
197 +React.jsx("div", {
198 + children: "w < w"
199 +});
200 +`;
201 +
202 +exports[`transform react to jsx should handle attributed elements 1`] = `
203 +var HelloMessage = React.createClass({
204 + render: function () {
205 + return React.jsxs("div", {
206 + children: ["Hello ", this.props.name]
207 + });
208 + }
209 +});
210 +React.render(React.jsx(HelloMessage, {
211 + name: React.jsx("span", {
212 + children: "Sebastian"
213 + })
214 +}), mountNode);
215 +`;
216 +
217 +exports[`transform react to jsx should handle has own property correctly 1`] = `
218 +React.jsx("hasOwnProperty", {
219 + children: "testing"
220 +});
221 +`;
222 +
223 +exports[`transform react to jsx should have correct comma in nested children 1`] = `
224 +var x = React.jsxs("div", {
225 + children: [React.jsx("div", {
226 + children: React.jsx("br", {})
227 + }), React.jsxs(Component, {
228 + children: [foo, React.jsx("br", {}), bar]
229 + }), React.jsx("br", {})]
230 +});
231 +`;
232 +
233 +exports[`transform react to jsx should insert commas after expressions before whitespace 1`] = `
234 +var x = React.jsx("div", {
235 + attr1: "foo" + "bar",
236 + attr2: "foo" + "bar" + "baz" + "bug",
237 + attr3: "foo" + "bar" + "baz" + "bug",
238 + attr4: "baz"
239 +});
240 +`;
241 +
242 +exports[`transform react to jsx should not add quotes to identifier names 1`] = `
243 +var e = React.jsx(F, {
244 + aaa: true,
245 + new: true,
246 + const: true,
247 + var: true,
248 + default: true,
249 + "foo-bar": true
250 +});
251 +`;
252 +
253 +exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = `
254 +React.jsx("div", {
255 + children: "\\xA0 "
256 +});
257 +`;
258 +
259 +exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = `
260 +React.jsx("div", {
261 + children: "\\xA0"
262 +});
263 +`;
264 +
265 +exports[`transform react to jsx should properly handle comments adjacent to children 1`] = `
266 +var x = React.jsxs("div", {
267 + children: [React.jsx("span", {}), React.jsx("br", {})]
268 +});
269 +`;
270 +
271 +exports[`transform react to jsx should properly handle comments between props 1`] = `
272 +var x = React.jsx("div", {
273 + /* a multi-line
274 + comment */
275 + attr1: "foo",
276 + children: React.jsx("span", {
277 + // a double-slash comment
278 + attr2: "bar"
279 + })
280 +});
281 +`;
282 +
283 +exports[`transform react to jsx should properly handle potentially null variables 1`] = `
284 +var foo = null;
285 +var x = React.jsx("div", Object.assign({}, foo));
286 +`;
287 +
288 +exports[`transform react to jsx should quote jsx attributes 1`] = `
289 +React.jsx("button", {
290 + "data-value": "a value",
291 + children: "Button"
292 +});
293 +`;
294 +
295 +exports[`transform react to jsx should support xml namespaces if flag 1`] = `
296 +React.jsx("f:image", {
297 + "n:attr": true
298 +});
299 +`;
300 +
301 +exports[`transform react to jsx should transform known hyphenated tags 1`] = `React.jsx("font-face", {});`;
302 +
303 +exports[`transform react to jsx static children 1`] = `
304 +var _jsxFileName = "";
305 +var x = React.jsxDEV("div", {
306 + children: [React.jsxDEV("span", {}, undefined, false, {
307 + fileName: _jsxFileName,
308 + lineNumber: 3
309 + }, this), [React.jsxDEV("span", {}, '0', false, {
310 + fileName: _jsxFileName,
311 + lineNumber: 4
312 + }, this), React.jsxDEV("span", {}, '1', false, {
313 + fileName: _jsxFileName,
314 + lineNumber: 4
315 + }, this)]]
316 +}, undefined, true, {
317 + fileName: _jsxFileName,
318 + lineNumber: 2
319 +}, this);
320 +`;
321 +
322 +exports[`transform react to jsx useBuiltIns false uses extend instead of Object.assign 1`] = `
323 +function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
324 +
325 +React.jsx(Component, _extends({
326 + y: 2
327 +}, x));
328 +`;
329 +
330 +exports[`transform react to jsx uses createElement when the key comes after a spread 1`] = `
331 +var x = React.createElement("div", Object.assign({}, props, {
332 + key: "1",
333 + foo: "bar"
334 +}));
335 +`;
336 +
337 +exports[`transform react to jsx uses jsx when the key comes before a spread 1`] = `
338 +var x = React.jsx("div", Object.assign({}, props, {
339 + foo: "bar"
340 +}), "1");
341 +`;
342 +
343 +exports[`transform react to jsx uses jsxDEV instead of jsx in dev mode 1`] = `
344 +var _jsxFileName = "";
345 +var x = React.jsxDEV("span", {
346 + propOne: "one",
347 + children: "Hi"
348 +}, undefined, false, {
349 + fileName: _jsxFileName,
350 + lineNumber: 1
351 +}, this);
352 +`;
353 +
354 +exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = `
355 +React.jsx(Component, Object.assign({}, x, {
356 + y: 2,
357 + z: true
358 +}));
359 +`;
360 +
361 +exports[`transform react to jsx wraps props in react spread for last spread attributes 1`] = `
362 +React.jsx(Component, Object.assign({
363 + y: 2,
364 + z: true
365 +}, x));
366 +`;
367 +
368 +exports[`transform react to jsx wraps props in react spread for middle spread attributes 1`] = `
369 +React.jsx(Component, Object.assign({
370 + y: 2
371 +}, x, {
372 + z: true
373 +}));
374 +`;
packages/babel-plugin-react-jsx/index.js new
+3
@@ -0,0 +1,3 @@
1 +'use strict';
2 +
3 +module.exports = require('./src/TransformJSXToReactBabelPlugin');
packages/babel-plugin-react-jsx/npm/index.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/react-jsx-babel.production.min.js');
5 +} else {
6 + module.exports = require('./cjs/react-jsx-babel.development.js');
7 +}
packages/babel-plugin-react-jsx/package.json new
+18
@@ -0,0 +1,18 @@
1 +{
2 + "name": "babel-plugin-react-jsx",
3 + "version": "0.1.0",
4 + "private": true,
5 + "description": "@babel/plugin-transform-react-jsx",
6 + "main": "index.js",
7 + "dependencies": {
8 + "esutils": "^2.0.0"
9 +
10 + },
11 + "files": [
12 + "README.md",
13 + "index.js",
14 + "build-info.json",
15 + "cjs/",
16 + "umd/"
17 + ]
18 +}
packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js new
+611
@@ -0,0 +1,611 @@
1 +// MIT License
2 +
3 +// Copyright (c) 2014-present Sebastian McKenzie and other contributors
4 +
5 +// Permission is hereby granted, free of charge, to any person obtaining
6 +// a copy of this software and associated documentation files (the
7 +// "Software"), to deal in the Software without restriction, including
8 +// without limitation the rights to use, copy, modify, merge, publish,
9 +// distribute, sublicense, and/or sell copies of the Software, and to
10 +// permit persons to whom the Software is furnished to do so, subject to
11 +// the following conditions:
12 +
13 +// The above copyright notice and this permission notice shall be
14 +// included in all copies or substantial portions of the Software.
15 +
16 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 +// © 2019 GitHub, Inc.
24 +'use strict';
25 +
26 +const esutils = require('esutils');
27 +
28 +function helper(babel, opts) {
29 + const {types: t} = babel;
30 +
31 + const visitor = {};
32 +
33 + visitor.JSXNamespacedName = function(path, state) {
34 + const throwIfNamespace =
35 + state.opts.throwIfNamespace === undefined
36 + ? true
37 + : !!state.opts.throwIfNamespace;
38 + if (throwIfNamespace) {
39 + throw path.buildCodeFrameError(
40 + `Namespace tags are not supported by default. React's JSX doesn't support namespace tags. \
41 +You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
42 + );
43 + }
44 + };
45 +
46 + visitor.JSXSpreadChild = function(path) {
47 + throw path.buildCodeFrameError(
48 + 'Spread children are not supported in React.',
49 + );
50 + };
51 +
52 + visitor.JSXElement = {
53 + exit(path, file) {
54 + let callExpr;
55 + if (file.opts.useCreateElement || shouldUseCreateElement(path)) {
56 + callExpr = buildCreateElementCall(path, file);
57 + } else {
58 + callExpr = buildJSXElementCall(path, file);
59 + }
60 +
61 + if (callExpr) {
62 + path.replaceWith(t.inherits(callExpr, path.node));
63 + }
64 + },
65 + };
66 +
67 + visitor.JSXFragment = {
68 + exit(path, file) {
69 + if (opts.compat) {
70 + throw path.buildCodeFrameError(
71 + 'Fragment tags are only supported in React 16 and up.',
72 + );
73 + }
74 + let callExpr;
75 + if (file.opts.useCreateElement) {
76 + callExpr = buildCreateElementFragmentCall(path, file);
77 + } else {
78 + callExpr = buildJSXFragmentCall(path, file);
79 + }
80 +
81 + if (callExpr) {
82 + path.replaceWith(t.inherits(callExpr, path.node));
83 + }
84 + },
85 + };
86 +
87 + return visitor;
88 +
89 + function convertJSXIdentifier(node, parent) {
90 + if (t.isJSXIdentifier(node)) {
91 + if (node.name === 'this' && t.isReferenced(node, parent)) {
92 + return t.thisExpression();
93 + } else if (esutils.keyword.isIdentifierNameES6(node.name)) {
94 + node.type = 'Identifier';
95 + } else {
96 + return t.stringLiteral(node.name);
97 + }
98 + } else if (t.isJSXMemberExpression(node)) {
99 + return t.memberExpression(
100 + convertJSXIdentifier(node.object, node),
101 + convertJSXIdentifier(node.property, node),
102 + );
103 + } else if (t.isJSXNamespacedName(node)) {
104 + /**
105 + * If there is flag "throwIfNamespace"
106 + * print XMLNamespace like string literal
107 + */
108 + return t.stringLiteral(`${node.namespace.name}:${node.name.name}`);
109 + }
110 +
111 + return node;
112 + }
113 +
114 + function convertAttributeValue(node) {
115 + if (t.isJSXExpressionContainer(node)) {
116 + return node.expression;
117 + } else {
118 + return node;
119 + }
120 + }
121 +
122 + function convertAttribute(node) {
123 + const value = convertAttributeValue(node.value || t.booleanLiteral(true));
124 +
125 + if (t.isStringLiteral(value) && !t.isJSXExpressionContainer(node.value)) {
126 + value.value = value.value.replace(/\n\s+/g, ' ');
127 +
128 + // "raw" JSXText should not be used from a StringLiteral because it needs to be escaped.
129 + if (value.extra && value.extra.raw) {
130 + delete value.extra.raw;
131 + }
132 + }
133 +
134 + if (t.isJSXNamespacedName(node.name)) {
135 + node.name = t.stringLiteral(
136 + node.name.namespace.name + ':' + node.name.name.name,
137 + );
138 + } else if (esutils.keyword.isIdentifierNameES6(node.name.name)) {
139 + node.name.type = 'Identifier';
140 + } else {
141 + node.name = t.stringLiteral(node.name.name);
142 + }
143 +
144 + return t.inherits(t.objectProperty(node.name, value), node);
145 + }
146 +
147 + // We want to use React.createElement, even in the case of
148 + // jsx, for <div {...props} key={key} /> to distinguish it
149 + // from <div key={key} {...props} />. This is an intermediary
150 + // step while we deprecate key spread from props. Afterwards,
151 + // we will remove createElement entirely
152 + function shouldUseCreateElement(path) {
153 + const openingPath = path.get('openingElement');
154 + const attributes = openingPath.node.attributes;
155 +
156 + let seenPropsSpread = false;
157 + for (let i = 0; i < attributes.length; i++) {
158 + const attr = attributes[i];
159 + if (
160 + seenPropsSpread &&
161 + t.isJSXAttribute(attr) &&
162 + attr.name.name === 'key'
163 + ) {
164 + return true;
165 + } else if (t.isJSXSpreadAttribute(attr)) {
166 + seenPropsSpread = true;
167 + }
168 + }
169 + return false;
170 + }
171 +
172 + // Builds JSX into:
173 + // Production: React.jsx(type, arguments, key)
174 + // Development: React.jsxDEV(type, arguments, key, isStaticChildren, source, self)
175 + function buildJSXElementCall(path, file) {
176 + if (opts.filter && !opts.filter(path.node, file)) {
177 + return;
178 + }
179 +
180 + const openingPath = path.get('openingElement');
181 + openingPath.parent.children = t.react.buildChildren(openingPath.parent);
182 +
183 + const tagExpr = convertJSXIdentifier(
184 + openingPath.node.name,
185 + openingPath.node,
186 + );
187 + const args = [];
188 +
189 + let tagName;
190 + if (t.isIdentifier(tagExpr)) {
191 + tagName = tagExpr.name;
192 + } else if (t.isLiteral(tagExpr)) {
193 + tagName = tagExpr.value;
194 + }
195 +
196 + const state = {
197 + tagExpr: tagExpr,
198 + tagName: tagName,
199 + args: args,
200 + };
201 +
202 + if (opts.pre) {
203 + opts.pre(state, file);
204 + }
205 +
206 + let attribs = [];
207 + let key;
208 + let source;
209 + let self;
210 +
211 + // for React.jsx, key, __source (dev), and __self (dev) is passed in as
212 + // a separate argument rather than in the args object. We go through the
213 + // props and filter out these three keywords so we can pass them in
214 + // as separate arguments later
215 + for (let i = 0; i < openingPath.node.attributes.length; i++) {
216 + const attr = openingPath.node.attributes[i];
217 + if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
218 + if (attr.name.name === 'key') {
219 + key = convertAttribute(attr).value;
220 + } else if (attr.name.name === '__source') {
221 + source = convertAttribute(attr).value;
222 + } else if (attr.name.name === '__self') {
223 + self = convertAttribute(attr).value;
224 + } else {
225 + attribs.push(attr);
226 + }
227 + } else {
228 + attribs.push(attr);
229 + }
230 + }
231 +
232 + if (attribs.length || path.node.children.length) {
233 + attribs = buildJSXOpeningElementAttributes(
234 + attribs,
235 + file,
236 + path.node.children,
237 + );
238 + } else {
239 + // attributes should never be null
240 + attribs = t.objectExpression([]);
241 + }
242 +
243 + args.push(attribs);
244 +
245 + if (!file.opts.development) {
246 + if (key !== undefined) {
247 + args.push(key);
248 + }
249 + } else {
250 + // isStaticChildren, __source, and __self are only used in development
251 + args.push(
252 + key === undefined ? t.identifier('undefined') : key,
253 + t.booleanLiteral(path.node.children.length > 1),
254 + source === undefined ? t.identifier('undefined') : source,
255 + self === undefined ? t.identifier('undefined') : self,
256 + );
257 + }
258 +
259 + if (opts.post) {
260 + opts.post(state, file);
261 + }
262 + return (
263 + state.call ||
264 + t.callExpression(
265 + path.node.children.length > 1 ? state.staticCallee : state.callee,
266 + args,
267 + )
268 + );
269 + }
270 +
271 + // Builds props for React.jsx. This function adds children into the props
272 + // and ensures that props is always an object
273 + function buildJSXOpeningElementAttributes(attribs, file, children) {
274 + let _props = [];
275 + const objs = [];
276 +
277 + const useBuiltIns = file.opts.useBuiltIns || false;
278 + if (typeof useBuiltIns !== 'boolean') {
279 + throw new Error(
280 + 'transform-react-jsx currently only accepts a boolean option for ' +
281 + 'useBuiltIns (defaults to false)',
282 + );
283 + }
284 +
285 + while (attribs.length) {
286 + const prop = attribs.shift();
287 + if (t.isJSXSpreadAttribute(prop)) {
288 + _props = pushProps(_props, objs);
289 + objs.push(prop.argument);
290 + } else {
291 + _props.push(convertAttribute(prop));
292 + }
293 + }
294 +
295 + // In React.JSX, children is no longer a separate argument, but passed in
296 + // through the argument object
297 + if (children && children.length > 0) {
298 + if (children.length === 1) {
299 + _props.push(t.objectProperty(t.identifier('children'), children[0]));
300 + } else {
301 + _props.push(
302 + t.objectProperty(
303 + t.identifier('children'),
304 + t.arrayExpression(children),
305 + ),
306 + );
307 + }
308 + }
309 +
310 + pushProps(_props, objs);
311 +
312 + if (objs.length === 1) {
313 + // only one object
314 + if (!t.isObjectExpression(objs[0])) {
315 + // if the prop object isn't an object, use Object.assign or _extends
316 + // to ensure that the prop will always be an object (as opposed to a variable
317 + // that could be null at some point)
318 + const expressionHelper = useBuiltIns
319 + ? t.memberExpression(t.identifier('Object'), t.identifier('assign'))
320 + : file.addHelper('extends');
321 +
322 + attribs = t.callExpression(expressionHelper, [
323 + t.objectExpression([]),
324 + objs[0],
325 + ]);
326 + } else {
327 + attribs = objs[0];
328 + }
329 + } else {
330 + // looks like we have multiple objects
331 + if (!t.isObjectExpression(objs[0])) {
332 + objs.unshift(t.objectExpression([]));
333 + }
334 +
335 + const expressionHelper = useBuiltIns
336 + ? t.memberExpression(t.identifier('Object'), t.identifier('assign'))
337 + : file.addHelper('extends');
338 +
339 + // spread it
340 + attribs = t.callExpression(expressionHelper, objs);
341 + }
342 +
343 + return attribs;
344 + }
345 +
346 + // Builds JSX Fragment <></> into
347 + // Production: React.jsx(type, arguments)
348 + // Development: React.jsxDEV(type, { children})
349 + function buildJSXFragmentCall(path, file) {
350 + if (opts.filter && !opts.filter(path.node, file)) {
351 + return;
352 + }
353 +
354 + const openingPath = path.get('openingElement');
355 + openingPath.parent.children = t.react.buildChildren(openingPath.parent);
356 +
357 + const args = [];
358 + const tagName = null;
359 + const tagExpr = file.get('jsxFragIdentifier')();
360 +
361 + const state = {
362 + tagExpr: tagExpr,
363 + tagName: tagName,
364 + args: args,
365 + };
366 +
367 + if (opts.pre) {
368 + opts.pre(state, file);
369 + }
370 +
371 + let childrenNode;
372 + if (path.node.children.length > 0) {
373 + if (path.node.children.length === 1) {
374 + childrenNode = path.node.children[0];
375 + } else {
376 + childrenNode = t.arrayExpression(path.node.children);
377 + }
378 + }
379 +
380 + args.push(
381 + t.objectExpression(
382 + childrenNode !== undefined
383 + ? [t.objectProperty(t.identifier('children'), childrenNode)]
384 + : [],
385 + ),
386 + );
387 +
388 + if (file.opts.development) {
389 + args.push(
390 + t.identifier('undefined'),
391 + t.booleanLiteral(path.node.children.length > 1),
392 + );
393 + }
394 +
395 + if (opts.post) {
396 + opts.post(state, file);
397 + }
398 +
399 + return (
400 + state.call ||
401 + t.callExpression(
402 + path.node.children.length > 1 ? state.staticCallee : state.callee,
403 + args,
404 + )
405 + );
406 + }
407 +
408 + // Builds JSX into:
409 + // Production: React.createElement(type, arguments, children)
410 + // Development: React.createElement(type, arguments, children, source, self)
411 + function buildCreateElementCall(path, file) {
412 + if (opts.filter && !opts.filter(path.node, file)) {
413 + return;
414 + }
415 +
416 + const openingPath = path.get('openingElement');
417 + openingPath.parent.children = t.react.buildChildren(openingPath.parent);
418 +
419 + const tagExpr = convertJSXIdentifier(
420 + openingPath.node.name,
421 + openingPath.node,
422 + );
423 + const args = [];
424 +
425 + let tagName;
426 + if (t.isIdentifier(tagExpr)) {
427 + tagName = tagExpr.name;
428 + } else if (t.isLiteral(tagExpr)) {
429 + tagName = tagExpr.value;
430 + }
431 +
432 + const state = {
433 + tagExpr: tagExpr,
434 + tagName: tagName,
435 + args: args,
436 + };
437 +
438 + if (opts.pre) {
439 + opts.pre(state, file);
440 + }
441 +
442 + let attribs = openingPath.node.attributes;
443 + if (attribs.length) {
444 + attribs = buildCreateElementOpeningElementAttributes(attribs, file);
445 + } else {
446 + attribs = t.nullLiteral();
447 + }
448 +
449 + args.push(attribs, ...path.node.children);
450 +
451 + if (opts.post) {
452 + opts.post(state, file);
453 + }
454 +
455 + return state.call || t.callExpression(state.oldCallee, args);
456 + }
457 +
458 + function pushProps(_props, objs) {
459 + if (!_props.length) {
460 + return _props;
461 + }
462 +
463 + objs.push(t.objectExpression(_props));
464 + return [];
465 + }
466 +
467 + /**
468 + * The logic for this is quite terse. It's because we need to
469 + * support spread elements. We loop over all attributes,
470 + * breaking on spreads, we then push a new object containing
471 + * all prior attributes to an array for later processing.
472 + */
473 + function buildCreateElementOpeningElementAttributes(attribs, file) {
474 + let _props = [];
475 + const objs = [];
476 +
477 + const useBuiltIns = file.opts.useBuiltIns || false;
478 + if (typeof useBuiltIns !== 'boolean') {
479 + throw new Error(
480 + 'transform-react-jsx currently only accepts a boolean option for ' +
481 + 'useBuiltIns (defaults to false)',
482 + );
483 + }
484 +
485 + while (attribs.length) {
486 + const prop = attribs.shift();
487 + if (t.isJSXSpreadAttribute(prop)) {
488 + _props = pushProps(_props, objs);
489 + objs.push(prop.argument);
490 + } else {
491 + const attr = convertAttribute(prop);
492 + _props.push(attr);
493 + }
494 + }
495 +
496 + pushProps(_props, objs);
497 +
498 + if (objs.length === 1) {
499 + // only one object
500 + attribs = objs[0];
501 + } else {
502 + // looks like we have multiple objects
503 + if (!t.isObjectExpression(objs[0])) {
504 + objs.unshift(t.objectExpression([]));
505 + }
506 +
507 + const expressionHelper = useBuiltIns
508 + ? t.memberExpression(t.identifier('Object'), t.identifier('assign'))
509 + : file.addHelper('extends');
510 +
511 + // spread it
512 + attribs = t.callExpression(expressionHelper, objs);
513 + }
514 +
515 + return attribs;
516 + }
517 +
518 + function buildCreateElementFragmentCall(path, file) {
519 + if (opts.filter && !opts.filter(path.node, file)) {
520 + return;
521 + }
522 +
523 + const openingPath = path.get('openingElement');
524 + openingPath.parent.children = t.react.buildChildren(openingPath.parent);
525 +
526 + const args = [];
527 + const tagName = null;
528 + const tagExpr = file.get('jsxFragIdentifier')();
529 +
530 + const state = {
531 + tagExpr: tagExpr,
532 + tagName: tagName,
533 + args: args,
534 + };
535 +
536 + if (opts.pre) {
537 + opts.pre(state, file);
538 + }
539 +
540 + // no attributes are allowed with <> syntax
541 + args.push(t.nullLiteral(), ...path.node.children);
542 +
543 + if (opts.post) {
544 + opts.post(state, file);
545 + }
546 +
547 + return state.call || t.callExpression(state.oldCallee, args);
548 + }
549 +}
550 +
551 +module.exports = function(babel) {
552 + const {types: t} = babel;
553 +
554 + const createIdentifierParser = id => () => {
555 + return id
556 + .split('.')
557 + .map(name => t.identifier(name))
558 + .reduce((object, property) => t.memberExpression(object, property));
559 + };
560 +
561 + const visitor = helper(babel, {
562 + pre(state) {
563 + const tagName = state.tagName;
564 + const args = state.args;
565 + if (t.react.isCompatTag(tagName)) {
566 + args.push(t.stringLiteral(tagName));
567 + } else {
568 + args.push(state.tagExpr);
569 + }
570 + },
571 +
572 + post(state, pass) {
573 + state.callee = pass.get('jsxIdentifier')();
574 + state.staticCallee = pass.get('jsxStaticIdentifier')();
575 + state.oldCallee = pass.get('oldJSXIdentifier')();
576 + },
577 + });
578 +
579 + visitor.Program = {
580 + enter(path, state) {
581 + state.set(
582 + 'oldJSXIdentifier',
583 + createIdentifierParser('React.createElement'),
584 + );
585 + state.set(
586 + 'jsxIdentifier',
587 + createIdentifierParser(
588 + state.opts.development ? 'React.jsxDEV' : 'React.jsx',
589 + ),
590 + );
591 + state.set(
592 + 'jsxStaticIdentifier',
593 + createIdentifierParser(
594 + state.opts.development ? 'React.jsxDEV' : 'React.jsxs',
595 + ),
596 + );
597 + state.set('jsxFragIdentifier', createIdentifierParser('React.Fragment'));
598 + },
599 + };
600 +
601 + visitor.JSXAttribute = function(path) {
602 + if (t.isJSXElement(path.node.value)) {
603 + path.node.value = t.jsxExpressionContainer(path.node.value);
604 + }
605 + };
606 +
607 + return {
608 + name: 'transform-react-jsx',
609 + visitor,
610 + };
611 +};
packages/react/src/ReactElement.js
+24 -16
@@ -179,14 +179,24 @@ export function jsx(type, config, maybeKey) {
179 let key = null;
180 let ref = null;
181
182 - if (hasValidRef(config)) {
183 - ref = config.ref;
182 + // Currently, key can be spread in as a prop. This causes a potential
183 + // issue if key is also explicitly declared (ie. <div {...props} key="Hi" />
184 + // or <div key="Hi" {...props} /> ). We want to deprecate key spread,
185 + // but as an intermediary step, we will use jsxDEV for everything except
186 + // <div {...props} key="Hi" />, because we aren't currently able to tell if
187 + // key is explicitly declared to be undefined or not.
188 + if (maybeKey !== undefined) {
189 + key = '' + maybeKey;
190 }
191
192 if (hasValidKey(config)) {
193 key = '' + config.key;
194 }
195
196 + if (hasValidRef(config)) {
197 + ref = config.ref;
198 + }
199 +
200 // Remaining properties are added to a new props object
201 for (propName in config) {
202 if (
@@ -197,12 +207,6 @@ export function jsx(type, config, maybeKey) {
207 }
208 }
209
200 - // intentionally not checking if key was set above
201 - // this key is higher priority as it's static
202 - if (maybeKey !== undefined) {
203 - key = '' + maybeKey;
204 - }
205 -
210 // Resolve default props
211 if (type && type.defaultProps) {
212 const defaultProps = type.defaultProps;
@@ -239,14 +243,24 @@ export function jsxDEV(type, config, maybeKey, source, self) {
243 let key = null;
244 let ref = null;
245
242 - if (hasValidRef(config)) {
243 - ref = config.ref;
246 + // Currently, key can be spread in as a prop. This causes a potential
247 + // issue if key is also explicitly declared (ie. <div {...props} key="Hi" />
248 + // or <div key="Hi" {...props} /> ). We want to deprecate key spread,
249 + // but as an intermediary step, we will use jsxDEV for everything except
250 + // <div {...props} key="Hi" />, because we aren't currently able to tell if
251 + // key is explicitly declared to be undefined or not.
252 + if (maybeKey !== undefined) {
253 + key = '' + maybeKey;
254 }
255
256 if (hasValidKey(config)) {
257 key = '' + config.key;
258 }
259
260 + if (hasValidRef(config)) {
261 + ref = config.ref;
262 + }
263 +
264 // Remaining properties are added to a new props object
265 for (propName in config) {
266 if (
@@ -257,12 +271,6 @@ export function jsxDEV(type, config, maybeKey, source, self) {
271 }
272 }
273
260 - // intentionally not checking if key was set above
261 - // this key is higher priority as it's static
262 - if (maybeKey !== undefined) {
263 - key = '' + maybeKey;
264 - }
265 -
274 // Resolve default props
275 if (type && type.defaultProps) {
276 const defaultProps = type.defaultProps;
packages/react/src/ReactElementValidator.js
+19 -3
@@ -43,6 +43,8 @@ if (__DEV__) {
43 propTypesMisspellWarningShown = false;
44 }
45
46 +const hasOwnProperty = Object.prototype.hasOwnProperty;
47 +
48 function getDeclarationErrorAddendum() {
49 if (ReactCurrentOwner.current) {
50 const name = getComponentName(ReactCurrentOwner.current.type);
@@ -334,12 +336,26 @@ export function jsxWithValidation(
336 // We don't want exception behavior to differ between dev and prod.
337 // (Rendering will throw with a helpful message and as soon as the type is
338 // fixed, the key warnings will appear.)
339 +
340 if (validType) {
341 const children = props.children;
342 if (children !== undefined) {
343 if (isStaticChildren) {
341 - for (let i = 0; i < children.length; i++) {
342 - validateChildKeys(children[i], type);
344 + if (Array.isArray(children)) {
345 + for (let i = 0; i < children.length; i++) {
346 + validateChildKeys(children[i], type);
347 + }
348 +
349 + if (Object.freeze) {
350 + Object.freeze(children);
351 + }
352 + } else {
353 + warning(
354 + false,
355 + 'React.jsx: Static children should always be an array. ' +
356 + 'You are likely explicitly calling React.jsxs or React.jsxDEV. ' +
357 + 'Use the Babel transform instead.',
358 + );
359 }
360 } else {
361 validateChildKeys(children, type);
@@ -347,7 +363,7 @@ export function jsxWithValidation(
363 }
364 }
365
350 - if (props.key !== undefined) {
366 + if (hasOwnProperty.call(props, 'key')) {
367 warning(
368 false,
369 'React.jsx: Spreading a key to JSX is a deprecated pattern. ' +
packages/react/src/__tests__/ReactElementJSX-test.internal.js
+12
@@ -215,6 +215,18 @@ describe('ReactElement.jsx', () => {
215 );
216 });
217
218 + it('warns when a jsxs is passed something that is not an array', () => {
219 + const container = document.createElement('div');
220 + expect(() =>
221 + ReactDOM.render(React.jsxs('div', {children: 'foo'}, null), container),
222 + ).toWarnDev(
223 + 'React.jsx: Static children should always be an array. ' +
224 + 'You are likely explicitly calling React.jsxs or React.jsxDEV. ' +
225 + 'Use the Babel transform instead.',
226 + {withoutStack: true},
227 + );
228 + });
229 +
230 it('should warn when `key` is being accessed on a host element', () => {
231 const element = React.jsxs('div', {}, '3');
232 expect(() => void element.props.key).toWarnDev(