prefer-stateless-function.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. /**
  2. * @fileoverview Enforce stateless components to be written as a pure function
  3. * @author Yannick Croissant
  4. */
  5. 'use strict';
  6. // ------------------------------------------------------------------------------
  7. // Requirements
  8. // ------------------------------------------------------------------------------
  9. const rule = require('../../../lib/rules/prefer-stateless-function');
  10. const RuleTester = require('eslint').RuleTester;
  11. const parserOptions = {
  12. ecmaVersion: 2018,
  13. sourceType: 'module',
  14. ecmaFeatures: {
  15. jsx: true
  16. }
  17. };
  18. // ------------------------------------------------------------------------------
  19. // Tests
  20. // ------------------------------------------------------------------------------
  21. const ruleTester = new RuleTester({parserOptions});
  22. ruleTester.run('prefer-stateless-function', rule, {
  23. valid: [
  24. {
  25. // Already a stateless function
  26. code: `
  27. const Foo = function(props) {
  28. return <div>{props.foo}</div>;
  29. };
  30. `
  31. }, {
  32. // Already a stateless (arrow) function
  33. code: 'const Foo = ({foo}) => <div>{foo}</div>;'
  34. }, {
  35. // Extends from PureComponent and uses props
  36. code: `
  37. class Foo extends React.PureComponent {
  38. render() {
  39. return <div>{this.props.foo}</div>;
  40. }
  41. }
  42. `,
  43. options: [{
  44. ignorePureComponents: true
  45. }]
  46. }, {
  47. // Extends from PureComponent and uses context
  48. code: `
  49. class Foo extends React.PureComponent {
  50. render() {
  51. return <div>{this.context.foo}</div>;
  52. }
  53. }
  54. `,
  55. options: [{
  56. ignorePureComponents: true
  57. }]
  58. }, {
  59. // Extends from PureComponent in an expression context.
  60. code: `
  61. const Foo = class extends React.PureComponent {
  62. render() {
  63. return <div>{this.props.foo}</div>;
  64. }
  65. };
  66. `,
  67. parserOptions: parserOptions,
  68. options: [{
  69. ignorePureComponents: true
  70. }]
  71. }, {
  72. // Has a lifecyle method
  73. code: `
  74. class Foo extends React.Component {
  75. shouldComponentUpdate() {
  76. return false;
  77. }
  78. render() {
  79. return <div>{this.props.foo}</div>;
  80. }
  81. }
  82. `
  83. }, {
  84. // Has a state
  85. code: `
  86. class Foo extends React.Component {
  87. changeState() {
  88. this.setState({foo: "clicked"});
  89. }
  90. render() {
  91. return <div onClick={this.changeState.bind(this)}>{this.state.foo || "bar"}</div>;
  92. }
  93. }
  94. `
  95. }, {
  96. // Use refs
  97. code: `
  98. class Foo extends React.Component {
  99. doStuff() {
  100. this.refs.foo.style.backgroundColor = "red";
  101. }
  102. render() {
  103. return <div ref="foo" onClick={this.doStuff}>{this.props.foo}</div>;
  104. }
  105. }
  106. `
  107. }, {
  108. // Has an additional method
  109. code: `
  110. class Foo extends React.Component {
  111. doStuff() {}
  112. render() {
  113. return <div>{this.props.foo}</div>;
  114. }
  115. }
  116. `
  117. }, {
  118. // Has an empty (no super) constructor
  119. code: `
  120. class Foo extends React.Component {
  121. constructor() {}
  122. render() {
  123. return <div>{this.props.foo}</div>;
  124. }
  125. }
  126. `
  127. }, {
  128. // Has a constructor
  129. code: `
  130. class Foo extends React.Component {
  131. constructor() {
  132. doSpecialStuffs();
  133. }
  134. render() {
  135. return <div>{this.props.foo}</div>;
  136. }
  137. }
  138. `
  139. }, {
  140. // Has a constructor (2)
  141. code: `
  142. class Foo extends React.Component {
  143. constructor() {
  144. foo;
  145. }
  146. render() {
  147. return <div>{this.props.foo}</div>;
  148. }
  149. }
  150. `
  151. }, {
  152. // Use this.bar
  153. code: `
  154. class Foo extends React.Component {
  155. render() {
  156. return <div>{this.bar}</div>;
  157. }
  158. }
  159. `,
  160. parser: 'babel-eslint'
  161. }, {
  162. // Use this.bar (destructuring)
  163. code: `
  164. class Foo extends React.Component {
  165. render() {
  166. let {props:{foo}, bar} = this;
  167. return <div>{foo}</div>;
  168. }
  169. }
  170. `,
  171. parser: 'babel-eslint'
  172. }, {
  173. // Use this[bar]
  174. code: `
  175. class Foo extends React.Component {
  176. render() {
  177. return <div>{this[bar]}</div>;
  178. }
  179. }
  180. `,
  181. parser: 'babel-eslint'
  182. }, {
  183. // Use this['bar']
  184. code: `
  185. class Foo extends React.Component {
  186. render() {
  187. return <div>{this['bar']}</div>;
  188. }
  189. }
  190. `,
  191. parser: 'babel-eslint'
  192. }, {
  193. // Can return null (ES6, React 0.14.0)
  194. code: `
  195. class Foo extends React.Component {
  196. render() {
  197. if (!this.props.foo) {
  198. return null;
  199. }
  200. return <div>{this.props.foo}</div>;
  201. }
  202. }
  203. `,
  204. parser: 'babel-eslint',
  205. settings: {
  206. react: {
  207. version: '0.14.0'
  208. }
  209. }
  210. }, {
  211. // Can return null (ES5, React 0.14.0)
  212. code: `
  213. var Foo = createReactClass({
  214. render: function() {
  215. if (!this.props.foo) {
  216. return null;
  217. }
  218. return <div>{this.props.foo}</div>;
  219. }
  220. });
  221. `,
  222. settings: {
  223. react: {
  224. version: '0.14.0'
  225. }
  226. }
  227. }, {
  228. // Can return null (shorthand if in return, React 0.14.0)
  229. code: `
  230. class Foo extends React.Component {
  231. render() {
  232. return true ? <div /> : null;
  233. }
  234. }
  235. `,
  236. parser: 'babel-eslint',
  237. settings: {
  238. react: {
  239. version: '0.14.0'
  240. }
  241. }
  242. }, {
  243. code: `
  244. export default (Component) => (
  245. class Test extends React.Component {
  246. componentDidMount() {}
  247. render() {
  248. return <Component />;
  249. }
  250. }
  251. );
  252. `,
  253. parser: 'babel-eslint'
  254. }, {
  255. // Has childContextTypes
  256. code: `
  257. class Foo extends React.Component {
  258. render() {
  259. return <div>{this.props.children}</div>;
  260. }
  261. }
  262. Foo.childContextTypes = {
  263. color: PropTypes.string
  264. };
  265. `,
  266. parser: 'babel-eslint'
  267. }, {
  268. // Uses a decorator
  269. code: `
  270. @foo
  271. class Foo extends React.Component {
  272. render() {
  273. return <div>{this.props.foo}</div>;
  274. }
  275. }
  276. `,
  277. parser: 'babel-eslint'
  278. }, {
  279. // Uses a called decorator
  280. code: `
  281. @foo("bar")
  282. class Foo extends React.Component {
  283. render() {
  284. return <div>{this.props.foo}</div>;
  285. }
  286. }
  287. `,
  288. parser: 'babel-eslint'
  289. }, {
  290. // Uses multiple decorators
  291. code: `
  292. @foo
  293. @bar()
  294. class Foo extends React.Component {
  295. render() {
  296. return <div>{this.props.foo}</div>;
  297. }
  298. }
  299. `,
  300. parser: 'babel-eslint'
  301. }
  302. ],
  303. invalid: [
  304. {
  305. // Only use this.props
  306. code: `
  307. class Foo extends React.Component {
  308. render() {
  309. return <div>{this.props.foo}</div>;
  310. }
  311. }
  312. `,
  313. errors: [{
  314. message: 'Component should be written as a pure function'
  315. }]
  316. }, {
  317. code: `
  318. class Foo extends React.Component {
  319. render() {
  320. return <div>{this['props'].foo}</div>;
  321. }
  322. }
  323. `,
  324. errors: [{
  325. message: 'Component should be written as a pure function'
  326. }]
  327. }, {
  328. code: `
  329. class Foo extends React.PureComponent {
  330. render() {
  331. return <div>foo</div>;
  332. }
  333. }
  334. `,
  335. options: [{
  336. ignorePureComponents: true
  337. }],
  338. errors: [{
  339. message: 'Component should be written as a pure function'
  340. }]
  341. }, {
  342. code: `
  343. class Foo extends React.PureComponent {
  344. render() {
  345. return <div>{this.props.foo}</div>;
  346. }
  347. }
  348. `,
  349. errors: [{
  350. message: 'Component should be written as a pure function'
  351. }]
  352. }, {
  353. code: `
  354. class Foo extends React.Component {
  355. static get displayName() {
  356. return 'Foo';
  357. }
  358. render() {
  359. return <div>{this.props.foo}</div>;
  360. }
  361. }
  362. `,
  363. parser: 'babel-eslint',
  364. errors: [{
  365. message: 'Component should be written as a pure function'
  366. }]
  367. }, {
  368. code: `
  369. class Foo extends React.Component {
  370. static displayName = 'Foo';
  371. render() {
  372. return <div>{this.props.foo}</div>;
  373. }
  374. }
  375. `,
  376. parser: 'babel-eslint',
  377. errors: [{
  378. message: 'Component should be written as a pure function'
  379. }]
  380. }, {
  381. code: `
  382. class Foo extends React.Component {
  383. static get propTypes() {
  384. return {
  385. name: PropTypes.string
  386. };
  387. }
  388. render() {
  389. return <div>{this.props.foo}</div>;
  390. }
  391. }
  392. `,
  393. parser: 'babel-eslint',
  394. errors: [{
  395. message: 'Component should be written as a pure function'
  396. }]
  397. }, {
  398. code: `
  399. class Foo extends React.Component {
  400. static propTypes = {
  401. name: PropTypes.string
  402. };
  403. render() {
  404. return <div>{this.props.foo}</div>;
  405. }
  406. }
  407. `,
  408. parser: 'babel-eslint',
  409. errors: [{
  410. message: 'Component should be written as a pure function'
  411. }]
  412. }, {
  413. code: `
  414. class Foo extends React.Component {
  415. props: {
  416. name: string;
  417. };
  418. render() {
  419. return <div>{this.props.foo}</div>;
  420. }
  421. }
  422. `,
  423. parser: 'babel-eslint',
  424. errors: [{
  425. message: 'Component should be written as a pure function'
  426. }]
  427. }, {
  428. code: `
  429. class Foo extends React.Component {
  430. constructor() {
  431. super();
  432. }
  433. render() {
  434. return <div>{this.props.foo}</div>;
  435. }
  436. }
  437. `,
  438. parser: 'babel-eslint',
  439. errors: [{
  440. message: 'Component should be written as a pure function'
  441. }]
  442. }, {
  443. code: `
  444. class Foo extends React.Component {
  445. render() {
  446. let {props:{foo}, context:{bar}} = this;
  447. return <div>{this.props.foo}</div>;
  448. }
  449. }
  450. `,
  451. errors: [{
  452. message: 'Component should be written as a pure function'
  453. }]
  454. }, {
  455. code: `
  456. class Foo extends React.Component {
  457. render() {
  458. if (!this.props.foo) {
  459. return null;
  460. }
  461. return <div>{this.props.foo}</div>;
  462. }
  463. }
  464. `,
  465. parser: 'babel-eslint',
  466. errors: [{
  467. message: 'Component should be written as a pure function'
  468. }]
  469. }, {
  470. code: `
  471. var Foo = createReactClass({
  472. render: function() {
  473. if (!this.props.foo) {
  474. return null;
  475. }
  476. return <div>{this.props.foo}</div>;
  477. }
  478. });
  479. `,
  480. errors: [{
  481. message: 'Component should be written as a pure function'
  482. }]
  483. }, {
  484. code: `
  485. class Foo extends React.Component {
  486. render() {
  487. return true ? <div /> : null;
  488. }
  489. }
  490. `,
  491. errors: [{
  492. message: 'Component should be written as a pure function'
  493. }]
  494. }, {
  495. code: `
  496. class Foo extends React.Component {
  497. static defaultProps = {
  498. foo: true
  499. }
  500. render() {
  501. const { foo } = this.props;
  502. return foo ? <div /> : null;
  503. }
  504. }
  505. `,
  506. parser: 'babel-eslint',
  507. errors: [{
  508. message: 'Component should be written as a pure function'
  509. }]
  510. }, {
  511. code: `
  512. class Foo extends React.Component {
  513. static get defaultProps() {
  514. return {
  515. foo: true
  516. };
  517. }
  518. render() {
  519. const { foo } = this.props;
  520. return foo ? <div /> : null;
  521. }
  522. }
  523. `,
  524. errors: [{
  525. message: 'Component should be written as a pure function'
  526. }]
  527. }, {
  528. code: `
  529. class Foo extends React.Component {
  530. render() {
  531. const { foo } = this.props;
  532. return foo ? <div /> : null;
  533. }
  534. }
  535. Foo.defaultProps = {
  536. foo: true
  537. };
  538. `,
  539. errors: [{
  540. message: 'Component should be written as a pure function'
  541. }]
  542. }, {
  543. code: `
  544. class Foo extends React.Component {
  545. static contextTypes = {
  546. foo: PropTypes.boolean
  547. }
  548. render() {
  549. const { foo } = this.context;
  550. return foo ? <div /> : null;
  551. }
  552. }
  553. `,
  554. parser: 'babel-eslint',
  555. errors: [{
  556. message: 'Component should be written as a pure function'
  557. }]
  558. }, {
  559. code: `
  560. class Foo extends React.Component {
  561. static get contextTypes() {
  562. return {
  563. foo: PropTypes.boolean
  564. };
  565. }
  566. render() {
  567. const { foo } = this.context;
  568. return foo ? <div /> : null;
  569. }
  570. }
  571. `,
  572. errors: [{
  573. message: 'Component should be written as a pure function'
  574. }]
  575. }, {
  576. code: `
  577. class Foo extends React.Component {
  578. render() {
  579. const { foo } = this.context;
  580. return foo ? <div /> : null;
  581. }
  582. }
  583. Foo.contextTypes = {
  584. foo: PropTypes.boolean
  585. };
  586. `,
  587. errors: [{
  588. message: 'Component should be written as a pure function'
  589. }]
  590. }
  591. ]
  592. });