Skip to content

Commit 7869530

Browse files
authored
Merge pull request #1831 from sergei-startsev/no-unsafe-rule
Added `no-unsafe` rule
2 parents 48e386d + 0285eef commit 7869530

File tree

5 files changed

+304
-0
lines changed

5 files changed

+304
-0
lines changed

README.md

+1
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`

docs/rules/no-unsafe.md

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Prevent usage of `UNSAFE_` methods (react/no-unsafe)
2+
3+
Certain legacy lifecycle methods are [unsafe for use in async React applications][async_rendering] 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+
[async_rendering]: https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
6+
[strict_mode]: https://reactjs.org/docs/strict-mode.html#identifying-unsafe-lifecycles
7+
[component_lifecycle_changes]: https://reactjs.org/blog/2018/03/29/react-v-16-3.html#component-lifecycle-changes
8+
9+
The rule checks the following methods: `UNSAFE_componentWillMount`, `UNSAFE_componentWillReceiveProps`, `UNSAFE_componentWillUpdate`.
10+
11+
## Rule Details
12+
13+
The following patterns are considered warnings:
14+
15+
```jsx
16+
class Foo extends React.Component {
17+
UNSAFE_componentWillMount() {}
18+
UNSAFE_componentWillReceiveProps() {}
19+
UNSAFE_componentWillUpdate() {}
20+
}
21+
```
22+
23+
```jsx
24+
const Foo = createReactClass({
25+
UNSAFE_componentWillMount: function() {},
26+
UNSAFE_componentWillReceiveProps: function() {},
27+
UNSAFE_componentWillUpdate: function() {}
28+
});
29+
```
30+
31+
The following patterns are **not** considered warnings:
32+
33+
```jsx
34+
class Foo extends Bar {
35+
UNSAFE_componentWillMount() {}
36+
UNSAFE_componentWillReceiveProps() {}
37+
UNSAFE_componentWillUpdate() {}
38+
}
39+
```
40+
41+
```jsx
42+
const Foo = bar({
43+
UNSAFE_componentWillMount: function() {},
44+
UNSAFE_componentWillReceiveProps: function() {},
45+
UNSAFE_componentWillUpdate: function() {}
46+
});
47+
```

index.js

+2
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': 0,
142144
'react/prop-types': 2,
143145
'react/react-in-jsx-scope': 2,
144146
'react/require-render-return': 2

lib/rules/no-unsafe.js

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

tests/lib/rules/no-unsafe.js

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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+
function errorMessage(method) {
23+
return `${method} is unsafe for use in async rendering, see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html`;
24+
}
25+
26+
// ------------------------------------------------------------------------------
27+
// Tests
28+
// ------------------------------------------------------------------------------
29+
30+
const ruleTester = new RuleTester({parserOptions});
31+
ruleTester.run('no-unsafe', rule, {
32+
valid: [
33+
{
34+
code: `
35+
class Foo extends React.Component {
36+
componentDidUpdate() {}
37+
render() {}
38+
}
39+
`,
40+
settings: {react: {version: '16.4.0'}}
41+
},
42+
{
43+
code: `
44+
const Foo = createReactClass({
45+
componentDidUpdate: function() {},
46+
render: function() {}
47+
});
48+
`,
49+
settings: {react: {version: '16.4.0'}}
50+
},
51+
{
52+
code: `
53+
class Foo extends Bar {
54+
UNSAFE_componentWillMount() {}
55+
UNSAFE_componentWillReceiveProps() {}
56+
UNSAFE_componentWillUpdate() {}
57+
}
58+
`,
59+
settings: {react: {version: '16.4.0'}}
60+
},
61+
{
62+
code: `
63+
const Foo = bar({
64+
UNSAFE_componentWillMount: function() {},
65+
UNSAFE_componentWillReceiveProps: function() {},
66+
UNSAFE_componentWillUpdate: function() {},
67+
});
68+
`,
69+
settings: {react: {version: '16.4.0'}}
70+
},
71+
{
72+
code: `
73+
class Foo extends React.Component {
74+
UNSAFE_componentWillMount() {}
75+
UNSAFE_componentWillReceiveProps() {}
76+
UNSAFE_componentWillUpdate() {}
77+
}
78+
`,
79+
settings: {react: {version: '16.2.0'}}
80+
},
81+
{
82+
code: `
83+
const Foo = createReactClass({
84+
UNSAFE_componentWillMount: function() {},
85+
UNSAFE_componentWillReceiveProps: function() {},
86+
UNSAFE_componentWillUpdate: function() {},
87+
});
88+
`,
89+
settings: {react: {version: '16.2.0'}}
90+
}
91+
],
92+
93+
invalid: [
94+
{
95+
code: `
96+
class Foo extends React.Component {
97+
UNSAFE_componentWillMount() {}
98+
UNSAFE_componentWillReceiveProps() {}
99+
UNSAFE_componentWillUpdate() {}
100+
}
101+
`,
102+
settings: {react: {version: '16.3.0'}},
103+
errors: [
104+
{
105+
message: errorMessage('UNSAFE_componentWillMount'),
106+
line: 2,
107+
column: 7,
108+
type: 'ClassDeclaration'
109+
},
110+
{
111+
message: errorMessage('UNSAFE_componentWillReceiveProps'),
112+
line: 2,
113+
column: 7,
114+
type: 'ClassDeclaration'
115+
},
116+
{
117+
message: errorMessage('UNSAFE_componentWillUpdate'),
118+
line: 2,
119+
column: 7,
120+
type: 'ClassDeclaration'
121+
}
122+
]
123+
},
124+
{
125+
code: `
126+
const Foo = createReactClass({
127+
UNSAFE_componentWillMount: function() {},
128+
UNSAFE_componentWillReceiveProps: function() {},
129+
UNSAFE_componentWillUpdate: function() {},
130+
});
131+
`,
132+
settings: {react: {version: '16.3.0'}},
133+
errors: [
134+
{
135+
message: errorMessage('UNSAFE_componentWillMount'),
136+
line: 2,
137+
column: 38,
138+
type: 'ObjectExpression'
139+
},
140+
{
141+
message: errorMessage('UNSAFE_componentWillReceiveProps'),
142+
line: 2,
143+
column: 38,
144+
type: 'ObjectExpression'
145+
},
146+
{
147+
message: errorMessage('UNSAFE_componentWillUpdate'),
148+
line: 2,
149+
column: 38,
150+
type: 'ObjectExpression'
151+
}
152+
]
153+
}
154+
]
155+
});

0 commit comments

Comments
 (0)