From b5df78381b7252d46815f73e976f98e0cc22a370 Mon Sep 17 00:00:00 2001 From: Joe Lencioni Date: Fri, 18 Dec 2015 17:08:43 -0800 Subject: [PATCH] Add no-is-mounted rule isMounted is an anti-pattern [0], is not available when using ES6 classes, and will eventually be officially deprecated. This commit adds a rule that prevents the use of this method. [0]: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html Finishes #37 --- README.md | 2 + docs/rules/no-is-mounted.md | 34 ++++++++++ index.js | 2 + lib/rules/no-is-mounted.js | 39 +++++++++++ tests/lib/rules/no-is-mounted.js | 110 +++++++++++++++++++++++++++++++ 5 files changed, 187 insertions(+) create mode 100644 docs/rules/no-is-mounted.md create mode 100644 lib/rules/no-is-mounted.js create mode 100644 tests/lib/rules/no-is-mounted.js diff --git a/README.md b/README.md index 48acbcadfd..f0939fab2d 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ Finally, enable all of the rules that you would like to use. "react/jsx-max-props-per-line": 1, "react/jsx-no-bind": 1, "react/jsx-no-duplicate-props": 1, + "react/jsx-no-is-mounted": 1, "react/jsx-no-literals": 1, "react/jsx-no-undef": 1, "react/jsx-pascal-case": 1, @@ -110,6 +111,7 @@ Finally, enable all of the rules that you would like to use. * [no-did-mount-set-state](docs/rules/no-did-mount-set-state.md): Prevent usage of `setState` in `componentDidMount` * [no-did-update-set-state](docs/rules/no-did-update-set-state.md): Prevent usage of `setState` in `componentDidUpdate` * [no-direct-mutation-state](docs/rules/no-direct-mutation-state.md): Prevent direct mutation of `this.state` +* [no-is-mounted](docs/rules/no-is-mounted.md): Prevent usage of `isMounted` * [no-multi-comp](docs/rules/no-multi-comp.md): Prevent multiple component definition per file * [no-set-state](docs/rules/no-set-state.md): Prevent usage of `setState` * [no-unknown-property](docs/rules/no-unknown-property.md): Prevent usage of unknown DOM property diff --git a/docs/rules/no-is-mounted.md b/docs/rules/no-is-mounted.md new file mode 100644 index 0000000000..365fc44167 --- /dev/null +++ b/docs/rules/no-is-mounted.md @@ -0,0 +1,34 @@ +# Prevent usage of isMounted (no-is-mounted) + +[`isMounted` is an anti-pattern](anti-pattern), is not available when using ES6 classes, and it is on its way to being officially deprecated. + +[anti-pattern]: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html + +## Rule Details + +The following patterns are considered warnings: + +```js +var Hello = React.createClass({ + handleClick: function() { + setTimeout(function() { + if (this.isMounted()) { + return; + } + }); + }, + render: function() { + return
Hello
; + } +}); +``` + +The following patterns are not considered warnings: + +```js +var Hello = React.createClass({ + render: function() { + return
Hello
; + } +}); +``` diff --git a/index.js b/index.js index 88a3d00299..e482777787 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,7 @@ module.exports = { 'self-closing-comp': require('./lib/rules/self-closing-comp'), 'no-danger': require('./lib/rules/no-danger'), 'no-set-state': require('./lib/rules/no-set-state'), + 'no-is-mounted': require('./lib/rules/no-is-mounted'), 'no-deprecated': require('./lib/rules/no-deprecated'), 'no-did-mount-set-state': require('./lib/rules/no-did-mount-set-state'), 'no-did-update-set-state': require('./lib/rules/no-did-update-set-state'), @@ -47,6 +48,7 @@ module.exports = { 'no-deprecated': 0, 'no-danger': 0, 'no-set-state': 0, + 'no-is-mounted': 0, 'no-did-mount-set-state': 0, 'no-did-update-set-state': 0, 'react-in-jsx-scope': 0, diff --git a/lib/rules/no-is-mounted.js b/lib/rules/no-is-mounted.js new file mode 100644 index 0000000000..bfd8c1de7a --- /dev/null +++ b/lib/rules/no-is-mounted.js @@ -0,0 +1,39 @@ +/** + * @fileoverview Prevent usage of isMounted + * @author Joe Lencioni + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = function(context) { + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + + CallExpression: function(node) { + var callee = node.callee; + if (callee.type !== 'MemberExpression') { + return; + } + if (callee.object.type !== 'ThisExpression' || callee.property.name !== 'isMounted') { + return; + } + var ancestors = context.getAncestors(callee); + for (var i = 0, j = ancestors.length; i < j; i++) { + if (ancestors[i].type === 'Property' || ancestors[i].type === 'MethodDefinition') { + context.report(callee, 'Do not use isMounted'); + break; + } + } + } + }; + +}; + +module.exports.schema = []; diff --git a/tests/lib/rules/no-is-mounted.js b/tests/lib/rules/no-is-mounted.js new file mode 100644 index 0000000000..9fd38fbcbe --- /dev/null +++ b/tests/lib/rules/no-is-mounted.js @@ -0,0 +1,110 @@ +/** + * @fileoverview Prevent usage of isMounted + * @author Joe Lencioni + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +var rule = require('../../../lib/rules/no-is-mounted'); +var RuleTester = require('eslint').RuleTester; + +var parserOptions = { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true + } +}; + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +var ruleTester = new RuleTester(); +ruleTester.run('no-is-mounted', rule, { + + valid: [{ + code: [ + 'var Hello = function() {', + '};' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'var Hello = React.createClass({', + ' render: function() {', + ' return
Hello
;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'var Hello = React.createClass({', + ' componentDidUpdate: function() {', + ' someNonMemberFunction(arg);', + ' this.someFunc = this.isMounted;', + ' },', + ' render: function() {', + ' return
Hello
;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions + }], + + invalid: [{ + code: [ + 'var Hello = React.createClass({', + ' componentDidUpdate: function() {', + ' if (!this.isMounted()) {', + ' return;', + ' }', + ' },', + ' render: function() {', + ' return
Hello
;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions, + errors: [{ + message: 'Do not use isMounted' + }] + }, { + code: [ + 'var Hello = React.createClass({', + ' someMethod: function() {', + ' if (!this.isMounted()) {', + ' return;', + ' }', + ' },', + ' render: function() {', + ' return
Hello
;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions, + errors: [{ + message: 'Do not use isMounted' + }] + }, { + code: [ + 'class Hello extends React.Component {', + ' someMethod() {', + ' if (!this.isMounted()) {', + ' return;', + ' }', + ' }', + ' render() {', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + parserOptions: parserOptions, + errors: [{ + message: 'Do not use isMounted' + }] + }] +});