jsx-curly-brace-presence.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. /**
  2. * @fileoverview Enforce curly braces or disallow unnecessary curly braces in JSX
  3. * @author Jacky Ho
  4. */
  5. 'use strict';
  6. /* eslint-disable quotes */ // For better readability on tests involving quotes
  7. // ------------------------------------------------------------------------------
  8. // Requirements
  9. // ------------------------------------------------------------------------------
  10. const rule = require('../../../lib/rules/jsx-curly-brace-presence');
  11. const RuleTester = require('eslint').RuleTester;
  12. const parserOptions = {
  13. sourceType: 'module',
  14. ecmaFeatures: {
  15. jsx: true
  16. }
  17. };
  18. const missingCurlyMessage = 'Need to wrap this literal in a JSX expression.';
  19. const unnecessaryCurlyMessage = 'Curly braces are unnecessary here.';
  20. // ------------------------------------------------------------------------------
  21. // Tests
  22. // ------------------------------------------------------------------------------
  23. const ruleTester = new RuleTester({parserOptions});
  24. ruleTester.run('jsx-curly-brace-presence', rule, {
  25. valid: [
  26. {
  27. code: '<App {...props}>foo</App>'
  28. },
  29. {
  30. code: '<App {...props}>foo</App>',
  31. options: [{props: 'never'}]
  32. },
  33. /*
  34. * There is no way to inject the space into JSX without an expression container
  35. * so this format should always be allowed regardless of the `children` option.
  36. */
  37. {
  38. code: '<App>{\' \'}</App>'
  39. },
  40. {
  41. code: '<App>{\' \'}</App>'
  42. },
  43. {
  44. code: '<App>{\' \'}</App>',
  45. options: [{children: 'never'}]
  46. },
  47. {
  48. code: '<App>{\' \'}</App>',
  49. options: [{children: 'never'}]
  50. },
  51. {
  52. code: '<App>{\' \'}</App>',
  53. options: [{children: 'always'}]
  54. },
  55. {
  56. code: '<App>{\' \'}</App>',
  57. options: [{children: 'always'}]
  58. },
  59. {
  60. code: '<App {...props}>foo</App>',
  61. options: [{props: 'always'}]
  62. },
  63. {
  64. code: '<App>{`Hello ${word} World`}</App>',
  65. options: [{children: 'never'}]
  66. },
  67. {
  68. code: '<App>{`Hello \\n World`}</App>',
  69. options: [{children: 'never'}]
  70. },
  71. {
  72. code: '<App>{`Hello ${word} World`}{`foo`}</App>',
  73. options: [{children: 'never'}]
  74. },
  75. {
  76. code: '<App prop={`foo ${word} bar`}>foo</App>',
  77. options: [{props: 'never'}]
  78. },
  79. {
  80. code: '<App prop={`foo ${word} bar`} />',
  81. options: [{props: 'never'}]
  82. },
  83. {
  84. code: '<App>{<myApp></myApp>}</App>'
  85. },
  86. {
  87. code: '<App>{[]}</App>'
  88. },
  89. {
  90. code: '<App>foo</App>'
  91. },
  92. {
  93. code: '<App>{"foo"}{<Component>bar</Component>}</App>'
  94. },
  95. {
  96. code: `<App prop='bar'>foo</App>`
  97. },
  98. {
  99. code: '<App prop={true}>foo</App>'
  100. },
  101. {
  102. code: '<App prop>foo</App>'
  103. },
  104. {
  105. code: `<App prop='bar'>{'foo \\n bar'}</App>`
  106. },
  107. {
  108. code: `<MyComponent prop='bar'>foo</MyComponent>`,
  109. options: [{props: 'never'}]
  110. },
  111. {
  112. code: `<MyComponent prop="bar">foo</MyComponent>`,
  113. options: [{props: 'never'}]
  114. },
  115. {
  116. code: '<MyComponent>foo</MyComponent>',
  117. options: [{children: 'never'}]
  118. },
  119. {
  120. code: '<MyComponent>{<App/>}{"123"}</MyComponent>',
  121. options: [{children: 'never'}]
  122. },
  123. {
  124. code: `<App>{"foo 'bar' \\"foo\\" bar"}</App>`,
  125. options: [{children: 'never'}]
  126. },
  127. {
  128. code: `<MyComponent prop={'bar'}>foo</MyComponent>`,
  129. options: [{props: 'always'}]
  130. },
  131. {
  132. code: `<MyComponent>{'foo'}</MyComponent>`,
  133. options: [{children: 'always'}]
  134. },
  135. {
  136. code: `<MyComponent prop={"bar"}>foo</MyComponent>`,
  137. options: [{props: 'always'}]
  138. },
  139. {
  140. code: `<MyComponent>{"foo"}</MyComponent>`,
  141. options: [{children: 'always'}]
  142. },
  143. {
  144. code: `<MyComponent>{'foo'}</MyComponent>`,
  145. options: [{children: 'ignore'}]
  146. },
  147. {
  148. code: `<MyComponent prop={'bar'}>foo</MyComponent>`,
  149. options: [{props: 'ignore'}]
  150. },
  151. {
  152. code: '<MyComponent>foo</MyComponent>',
  153. options: [{children: 'ignore'}]
  154. },
  155. {
  156. code: `<MyComponent prop='bar'>foo</MyComponent>`,
  157. options: [{props: 'ignore'}]
  158. },
  159. {
  160. code: `<MyComponent prop="bar">foo</MyComponent>`,
  161. options: [{props: 'ignore'}]
  162. },
  163. {
  164. code: `<MyComponent prop='bar'>{'foo'}</MyComponent>`,
  165. options: [{children: 'always', props: 'never'}]
  166. },
  167. {
  168. code: `<MyComponent prop={'bar'}>foo</MyComponent>`,
  169. options: [{children: 'never', props: 'always'}]
  170. },
  171. {
  172. code: `<MyComponent prop={'bar'}>{'foo'}</MyComponent>`,
  173. options: ['always']
  174. },
  175. {
  176. code: `<MyComponent prop={"bar"}>{"foo"}</MyComponent>`,
  177. options: ['always']
  178. },
  179. {
  180. code: `<MyComponent prop={"bar"} attr={'foo'} />`,
  181. options: ['always']
  182. },
  183. {
  184. code: `<MyComponent prop="bar" attr='foo' />`,
  185. options: ['never']
  186. },
  187. {
  188. code: `<MyComponent prop='bar'>foo</MyComponent>`,
  189. options: ['never']
  190. },
  191. {
  192. code: '<MyComponent prop={`bar ${word} foo`}>{`foo ${word}`}</MyComponent>',
  193. options: ['never']
  194. },
  195. {
  196. code: '<MyComponent>{"div { margin-top: 0; }"}</MyComponent>',
  197. options: ['never']
  198. },
  199. {
  200. code: '<MyComponent>{"<Foo />"}</MyComponent>',
  201. options: ['never']
  202. },
  203. {
  204. code: '<MyComponent prop={"{ style: true }"}>bar</MyComponent>',
  205. options: ['never']
  206. },
  207. {
  208. code: '<MyComponent prop={"< style: true >"}>foo</MyComponent>',
  209. options: ['never']
  210. },
  211. {
  212. code: '<MyComponent prop={"Hello \\u1026 world"}>bar</MyComponent>',
  213. options: ['never']
  214. },
  215. {
  216. code: '<MyComponent>{"Hello \\u1026 world"}</MyComponent>',
  217. options: ['never']
  218. },
  219. {
  220. code: '<MyComponent prop={"Hello &middot; world"}>bar</MyComponent>',
  221. options: ['never']
  222. },
  223. {
  224. code: '<MyComponent>{"Hello &middot; world"}</MyComponent>',
  225. options: ['never']
  226. },
  227. {
  228. code: '<MyComponent>{"Hello \\n world"}</MyComponent>',
  229. options: ['never']
  230. },
  231. {
  232. code: ['<a a={"start\\', '\\', 'end"}/>'].join('/n'),
  233. options: ['never']
  234. }
  235. ],
  236. invalid: [
  237. {
  238. code: '<App prop={`foo`} />',
  239. output: '<App prop="foo" />',
  240. options: [{props: 'never'}],
  241. errors: [{message: unnecessaryCurlyMessage}]
  242. },
  243. {
  244. code: '<App prop={`foo`}>foo</App>',
  245. output: '<App prop="foo">foo</App>',
  246. options: [{props: 'never'}],
  247. errors: [{message: unnecessaryCurlyMessage}]
  248. },
  249. {
  250. code: '<App>{`foo`}</App>',
  251. output: '<App>foo</App>',
  252. options: [{children: 'never'}],
  253. errors: [{message: unnecessaryCurlyMessage}]
  254. },
  255. {
  256. code: `<MyComponent>{'foo'}</MyComponent>`,
  257. output: '<MyComponent>foo</MyComponent>',
  258. errors: [{message: unnecessaryCurlyMessage}]
  259. },
  260. {
  261. code: `<MyComponent prop={'bar'}>foo</MyComponent>`,
  262. output: `<MyComponent prop="bar">foo</MyComponent>`,
  263. errors: [{message: unnecessaryCurlyMessage}]
  264. },
  265. {
  266. code: `<MyComponent>{'foo'}</MyComponent>`,
  267. output: '<MyComponent>foo</MyComponent>',
  268. options: [{children: 'never'}],
  269. errors: [{message: unnecessaryCurlyMessage}]
  270. },
  271. {
  272. code: `<MyComponent prop={'bar'}>foo</MyComponent>`,
  273. output: '<MyComponent prop="bar">foo</MyComponent>',
  274. options: [{props: 'never'}],
  275. errors: [{message: unnecessaryCurlyMessage}]
  276. },
  277. {
  278. code: `<MyComponent prop='bar'>foo</MyComponent>`,
  279. output: '<MyComponent prop={"bar"}>foo</MyComponent>',
  280. options: [{props: 'always'}],
  281. errors: [{message: missingCurlyMessage}]
  282. },
  283. {
  284. code: `<MyComponent prop="foo 'bar'">foo</MyComponent>`,
  285. output: `<MyComponent prop={"foo 'bar'"}>foo</MyComponent>`,
  286. options: [{props: 'always'}],
  287. errors: [{message: missingCurlyMessage}]
  288. },
  289. {
  290. code: `<MyComponent prop='foo "bar"'>foo</MyComponent>`,
  291. output: `<MyComponent prop={"foo \\"bar\\""}>foo</MyComponent>`,
  292. options: [{props: 'always'}],
  293. errors: [{message: missingCurlyMessage}]
  294. },
  295. {
  296. code: `<MyComponent prop="foo 'bar'">foo</MyComponent>`,
  297. output: `<MyComponent prop={"foo 'bar'"}>foo</MyComponent>`,
  298. options: [{props: 'always'}],
  299. errors: [{message: missingCurlyMessage}]
  300. },
  301. {
  302. code: '<MyComponent>foo bar </MyComponent>',
  303. output: `<MyComponent>{"foo bar "}</MyComponent>`,
  304. options: [{children: 'always'}],
  305. errors: [{message: missingCurlyMessage}]
  306. },
  307. {
  308. code: `<MyComponent prop="foo 'bar' \\n ">foo</MyComponent>`,
  309. output: `<MyComponent prop={"foo 'bar' \\\\n "}>foo</MyComponent>`,
  310. options: [{props: 'always'}],
  311. errors: [{message: missingCurlyMessage}]
  312. },
  313. {
  314. code: '<MyComponent>foo bar \\r </MyComponent>',
  315. output: '<MyComponent>{"foo bar \\\\r "}</MyComponent>',
  316. options: [{children: 'always'}],
  317. errors: [{message: missingCurlyMessage}]
  318. },
  319. {
  320. code: `<MyComponent>foo bar 'foo'</MyComponent>`,
  321. output: `<MyComponent>{"foo bar 'foo'"}</MyComponent>`,
  322. options: [{children: 'always'}],
  323. errors: [{message: missingCurlyMessage}]
  324. },
  325. {
  326. code: '<MyComponent>foo bar "foo"</MyComponent>',
  327. output: '<MyComponent>{"foo bar \\"foo\\""}</MyComponent>',
  328. options: [{children: 'always'}],
  329. errors: [{message: missingCurlyMessage}]
  330. },
  331. {
  332. code: '<MyComponent>foo bar <App/></MyComponent>',
  333. output: '<MyComponent>{"foo bar "}<App/></MyComponent>',
  334. options: [{children: 'always'}],
  335. errors: [{message: missingCurlyMessage}]
  336. },
  337. {
  338. code: '<MyComponent>foo \\n bar</MyComponent>',
  339. output: '<MyComponent>{"foo \\\\n bar"}</MyComponent>',
  340. options: [{children: 'always'}],
  341. errors: [{message: missingCurlyMessage}]
  342. },
  343. {
  344. code: '<MyComponent>foo \\u1234 bar</MyComponent>',
  345. output: '<MyComponent>{"foo \\\\u1234 bar"}</MyComponent>',
  346. options: [{children: 'always'}],
  347. errors: [{message: missingCurlyMessage}]
  348. },
  349. {
  350. code: `<MyComponent prop='foo \\u1234 bar' />`,
  351. output: '<MyComponent prop={"foo \\\\u1234 bar"} />',
  352. options: [{props: 'always'}],
  353. errors: [{message: missingCurlyMessage}]
  354. },
  355. {
  356. code: `<MyComponent prop={'bar'}>{'foo'}</MyComponent>`,
  357. output: '<MyComponent prop="bar">foo</MyComponent>',
  358. options: ['never'],
  359. errors: [
  360. {message: unnecessaryCurlyMessage}, {message: unnecessaryCurlyMessage}
  361. ]
  362. },
  363. {
  364. code: `<MyComponent prop='bar'>foo</MyComponent>`,
  365. output: '<MyComponent prop={"bar"}>{"foo"}</MyComponent>',
  366. options: ['always'],
  367. errors: [
  368. {message: missingCurlyMessage}, {message: missingCurlyMessage}
  369. ]
  370. },
  371. {
  372. code: `<App prop={'foo'} attr={" foo "} />`,
  373. output: '<App prop="foo" attr=" foo " />',
  374. errors: [
  375. {message: unnecessaryCurlyMessage}, {message: unnecessaryCurlyMessage}
  376. ],
  377. options: [{props: 'never'}]
  378. },
  379. {
  380. code: `<App prop='foo' attr="bar" />`,
  381. output: '<App prop={"foo"} attr={"bar"} />',
  382. errors: [
  383. {message: missingCurlyMessage}, {message: missingCurlyMessage}
  384. ],
  385. options: [{props: 'always'}]
  386. },
  387. {
  388. code: `<App prop='foo' attr={"bar"} />`,
  389. output: `<App prop={"foo"} attr={"bar"} />`,
  390. errors: [{message: missingCurlyMessage}],
  391. options: [{props: 'always'}]
  392. },
  393. {
  394. code: `<App prop={'foo'} attr='bar' />`,
  395. output: `<App prop={'foo'} attr={"bar"} />`,
  396. errors: [{message: missingCurlyMessage}],
  397. options: [{props: 'always'}]
  398. },
  399. {
  400. code: `<App prop='foo &middot; bar' />`,
  401. errors: [{message: missingCurlyMessage}],
  402. options: [{props: 'always'}]
  403. },
  404. {
  405. code: '<App>foo &middot; bar</App>',
  406. errors: [{message: missingCurlyMessage}],
  407. options: [{children: 'always'}]
  408. },
  409. {
  410. code: `<App>{'foo "bar"'}</App>`,
  411. output: `<App>foo "bar"</App>`,
  412. errors: [{message: unnecessaryCurlyMessage}],
  413. options: [{children: 'never'}]
  414. },
  415. {
  416. code: `<App>{"foo 'bar'"}</App>`,
  417. output: `<App>foo 'bar'</App>`,
  418. errors: [{message: unnecessaryCurlyMessage}],
  419. options: [{children: 'never'}]
  420. },
  421. {
  422. code: [
  423. '<App prop=" ',
  424. ' a ',
  425. ' b c',
  426. ' d',
  427. '">',
  428. ' a',
  429. ' b c ',
  430. ' d ',
  431. '</App>'
  432. ].join('\n'),
  433. errors: [
  434. {message: missingCurlyMessage}, {message: missingCurlyMessage}
  435. ],
  436. options: ['always']
  437. },
  438. {
  439. code: [
  440. `<App prop=' `,
  441. ' a ',
  442. ' b c',
  443. ' d',
  444. `'>`,
  445. ' a',
  446. ' b c ',
  447. ' d ',
  448. '</App>'
  449. ].join('\n'),
  450. errors: [
  451. {message: missingCurlyMessage}, {message: missingCurlyMessage}
  452. ],
  453. options: ['always']
  454. }
  455. ]
  456. });