Skip to content

Commit b5e86bf

Browse files
Added no-unsafe rule
1 parent 48e386d commit b5e86bf

File tree

5 files changed

+250
-0
lines changed

5 files changed

+250
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ Enable the rules that you would like to use.
117117
* [react/no-this-in-sfc](docs/rules/no-this-in-sfc.md): Prevent using `this` in stateless functional components
118118
* [react/no-unescaped-entities](docs/rules/no-unescaped-entities.md): Prevent invalid characters from appearing in markup
119119
* [react/no-unknown-property](docs/rules/no-unknown-property.md): Prevent usage of unknown DOM property (fixable)
120+
* [react/no-unsafe](docs/rules/no-unsafe.md): Prevent usage of `UNSAFE_` methods
120121
* [react/no-unused-prop-types](docs/rules/no-unused-prop-types.md): Prevent definitions of unused prop types
121122
* [react/no-unused-state](docs/rules/no-unused-state.md): Prevent definitions of unused state properties
122123
* [react/no-will-update-set-state](docs/rules/no-will-update-set-state.md): Prevent usage of `setState` in `componentWillUpdate`
@@ -208,6 +209,7 @@ The rules enabled in this configuration are:
208209
* [react/no-string-refs](docs/rules/no-string-refs.md)
209210
* [react/no-unescaped-entities](docs/rules/no-unescaped-entities.md)
210211
* [react/no-unknown-property](docs/rules/no-unknown-property.md)
212+
* [react/no-unsafe](docs/rules/no-unsafe.md)
211213
* [react/prop-types](docs/rules/prop-types.md)
212214
* [react/react-in-jsx-scope](docs/rules/react-in-jsx-scope.md)
213215
* [react/require-render-return](docs/rules/require-render-return.md)

docs/rules/no-unsafe.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Prevent usage of `UNSAFE_` methods (react/no-unsafe)
2+
3+
Certain legacy lifecycle methods are unsafe for use in async React applications and cause warnings in [_strict mode_][strict_mode]. These also happen to be the lifecycles that cause the most [confusion within the React community][component_lifecycle_changes].
4+
5+
[strict_mode]: https://reactjs.org/docs/strict-mode.html#identifying-unsafe-lifecycles
6+
[component_lifecycle_changes]: https://reactjs.org/blog/2018/03/29/react-v-16-3.html#component-lifecycle-changes
7+
8+
The rule checks the following methods: `UNSAFE_componentWillMount`, `UNSAFE_componentWillReceiveProps`, `UNSAFE_componentWillUpdate`.
9+
10+
## Rule Details
11+
12+
The following patterns are considered warnings:
13+
14+
```jsx
15+
class Foo extends React.Component {
16+
UNSAFE_componentWillMount() {}
17+
UNSAFE_componentWillReceiveProps() {}
18+
UNSAFE_componentWillUpdate() {}
19+
}
20+
```
21+
22+
```jsx
23+
const Foo = createReactClass({
24+
UNSAFE_componentWillMount: function() {},
25+
UNSAFE_componentWillReceiveProps: function() {},
26+
UNSAFE_componentWillUpdate: function() {}
27+
});
28+
```
29+
30+
The following patterns are **not** considered warnings:
31+
32+
```jsx
33+
class Foo extends Bar {
34+
UNSAFE_componentWillMount() {}
35+
UNSAFE_componentWillReceiveProps() {}
36+
UNSAFE_componentWillUpdate() {}
37+
}
38+
```
39+
40+
```jsx
41+
const Foo = bar({
42+
UNSAFE_componentWillMount: function() {},
43+
UNSAFE_componentWillReceiveProps: function() {},
44+
UNSAFE_componentWillUpdate: function() {}
45+
});
46+
```

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const allRules = {
6464
'no-typos': require('./lib/rules/no-typos'),
6565
'no-unescaped-entities': require('./lib/rules/no-unescaped-entities'),
6666
'no-unknown-property': require('./lib/rules/no-unknown-property'),
67+
'no-unsafe': require('./lib/rules/no-unsafe'),
6768
'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'),
6869
'no-unused-state': require('./lib/rules/no-unused-state'),
6970
'no-will-update-set-state': require('./lib/rules/no-will-update-set-state'),
@@ -139,6 +140,7 @@ module.exports = {
139140
'react/no-string-refs': 2,
140141
'react/no-unescaped-entities': 2,
141142
'react/no-unknown-property': 2,
143+
'react/no-unsafe': 2,
142144
'react/prop-types': 2,
143145
'react/react-in-jsx-scope': 2,
144146
'react/require-render-return': 2

lib/rules/no-unsafe.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* @fileoverview Prevent usage of UNSAFE_ methods
3+
* @author Sergei Startsev
4+
*/
5+
6+
'use strict';
7+
8+
const Components = require('../util/Components');
9+
const astUtil = require('../util/ast');
10+
const docsUrl = require('../util/docsUrl');
11+
12+
// ------------------------------------------------------------------------------
13+
// Rule Definition
14+
// ------------------------------------------------------------------------------
15+
16+
module.exports = {
17+
meta: {
18+
docs: {
19+
description: 'Prevent usage of UNSAFE_ methods',
20+
category: 'Best Practices',
21+
recommended: true,
22+
url: docsUrl('no-unsafe')
23+
},
24+
schema: []
25+
},
26+
27+
create: Components.detect((context, components, utils) => {
28+
/**
29+
* Returns a list of unsafe methods
30+
* @returns {Array} A list of unsafe methods
31+
*/
32+
function getUnsafeMethods() {
33+
return [
34+
'UNSAFE_componentWillMount',
35+
'UNSAFE_componentWillReceiveProps',
36+
'UNSAFE_componentWillUpdate'
37+
];
38+
}
39+
40+
/**
41+
* Checks if a passed method is unsafe
42+
* @param {string} method Life cycle method
43+
* @returns {boolean} Returns true for unsafe methods, otherwise returns false
44+
*/
45+
function isUnsafe(method) {
46+
const unsafeMethods = getUnsafeMethods();
47+
return unsafeMethods.indexOf(method) !== -1;
48+
}
49+
50+
/**
51+
* Reports the error for an unsafe method
52+
* @param {ASTNode} node The AST node being checked
53+
* @param {string} method Life cycle method
54+
*/
55+
function checkUnsafe(node, method) {
56+
if (!isUnsafe(method)) {
57+
return;
58+
}
59+
60+
context.report({
61+
node: node,
62+
message: `Do not use ${method}`
63+
});
64+
}
65+
66+
/**
67+
* Returns life cycle methods if available
68+
* @param {ASTNode} node The AST node being checked.
69+
* @returns {Array} The array of methods.
70+
*/
71+
function getLifeCycleMethods(node) {
72+
const properties = astUtil.getComponentProperties(node);
73+
return properties.map(property => astUtil.getPropertyName(property));
74+
}
75+
76+
/**
77+
* Checks life cycle methods
78+
* @param {ASTNode} node The AST node being checked.
79+
*/
80+
function checkLifeCycleMethods(node) {
81+
if (utils.isES5Component(node) || utils.isES6Component(node)) {
82+
const methods = getLifeCycleMethods(node);
83+
methods.forEach(method => checkUnsafe(node, method));
84+
}
85+
}
86+
87+
return {
88+
ClassDeclaration: checkLifeCycleMethods,
89+
ClassExpression: checkLifeCycleMethods,
90+
ObjectExpression: checkLifeCycleMethods
91+
};
92+
})
93+
};

tests/lib/rules/no-unsafe.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* @fileoverview Prevent usage of UNSAFE_ methods
3+
* @author Sergei Startsev
4+
*/
5+
'use strict';
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const rule = require('../../../lib/rules/no-unsafe');
12+
const RuleTester = require('eslint').RuleTester;
13+
14+
const parserOptions = {
15+
ecmaVersion: 2018,
16+
sourceType: 'module',
17+
ecmaFeatures: {
18+
jsx: true
19+
}
20+
};
21+
22+
// ------------------------------------------------------------------------------
23+
// Tests
24+
// ------------------------------------------------------------------------------
25+
26+
const ruleTester = new RuleTester({parserOptions});
27+
ruleTester.run('no-unsafe', rule, {
28+
valid: [
29+
{
30+
code: `
31+
class Foo extends React.Component {
32+
componentDidUpdate() {}
33+
render() {}
34+
}
35+
`
36+
},
37+
{
38+
code: `
39+
const Foo = createReactClass({
40+
componentDidUpdate: function() {},
41+
render: function() {}
42+
});
43+
`
44+
},
45+
{
46+
code: `
47+
class Foo extends Bar {
48+
UNSAFE_componentWillMount() {}
49+
UNSAFE_componentWillReceiveProps() {}
50+
UNSAFE_componentWillUpdate() {}
51+
}
52+
`
53+
},
54+
{
55+
code: `
56+
const Foo = bar({
57+
UNSAFE_componentWillMount: function() {},
58+
UNSAFE_componentWillReceiveProps: function() {},
59+
UNSAFE_componentWillUpdate: function() {},
60+
});
61+
`
62+
}
63+
],
64+
65+
invalid: [
66+
{
67+
code: `
68+
class Foo extends React.Component {
69+
UNSAFE_componentWillMount() {}
70+
UNSAFE_componentWillReceiveProps() {}
71+
UNSAFE_componentWillUpdate() {}
72+
}
73+
`,
74+
errors: [
75+
{
76+
message: 'Do not use UNSAFE_componentWillMount'
77+
},
78+
{
79+
message: 'Do not use UNSAFE_componentWillReceiveProps'
80+
},
81+
{
82+
message: 'Do not use UNSAFE_componentWillUpdate'
83+
}
84+
]
85+
},
86+
{
87+
code: `
88+
const Foo = createReactClass({
89+
UNSAFE_componentWillMount: function() {},
90+
UNSAFE_componentWillReceiveProps: function() {},
91+
UNSAFE_componentWillUpdate: function() {},
92+
});
93+
`,
94+
errors: [
95+
{
96+
message: 'Do not use UNSAFE_componentWillMount'
97+
},
98+
{
99+
message: 'Do not use UNSAFE_componentWillReceiveProps'
100+
},
101+
{
102+
message: 'Do not use UNSAFE_componentWillUpdate'
103+
}
104+
]
105+
}
106+
]
107+
});

0 commit comments

Comments
 (0)