Skip to content

Commit 65fd198

Browse files
committed
Add no-context rule (fixes #455)
1 parent 763382f commit 65fd198

File tree

5 files changed

+299
-0
lines changed

5 files changed

+299
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
8181

8282
* [react/display-name](docs/rules/display-name.md): Prevent missing `displayName` in a React component definition
8383
* [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes
84+
* [react/no-context](docs/rules/no-context.md): Prevent usage of `context`
8485
* [react/no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties
8586
* [react/no-deprecated](docs/rules/no-deprecated.md): Prevent usage of deprecated methods
8687
* [react/no-did-mount-set-state](docs/rules/no-did-mount-set-state.md): Prevent usage of `setState` in `componentDidMount`

docs/rules/no-context.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Prevent usage of context (no-context)
2+
3+
[Context](https://facebook.github.io/react/docs/context.html) is an advanced and experimental feature. The API is likely to change in future releases. Most applications will never need to use context. Especially if you are just getting started with React, you likely do not want to use context. Using context will make your code harder to understand because it makes the data flow less clear. It is similar to using global variables to pass state through your application.
4+
5+
## Rule Details
6+
7+
The following patterns are considered warnings:
8+
9+
```js
10+
class Button extends React.Component {
11+
render() {
12+
return (
13+
<button style={{background: this.context.color}}>
14+
{this.props.children}
15+
</button>
16+
);
17+
}
18+
}
19+
Button.contextTypes = {
20+
color: React.PropTypes.string
21+
};
22+
23+
const Button = ({children}, context) =>
24+
<button style={{background: context.color}}>
25+
{children}
26+
</button>;
27+
Button.contextTypes = {color: React.PropTypes.string};
28+
```
29+
30+
The following patterns are not considered warnings:
31+
32+
```js
33+
class Button extends React.Component {
34+
render() {
35+
return (
36+
<button style={{background: this.props.color}}>
37+
{this.props.children}
38+
</button>
39+
);
40+
}
41+
}
42+
Button.propTypes = {
43+
color: React.PropTypes.string
44+
};
45+
46+
const Button = ({children, color}, context) =>
47+
<button style={{background: color}}>
48+
{children}
49+
</button>;
50+
Button.propTypes = {color: React.PropTypes.string};
51+
```

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ var rules = {
1414
'jsx-wrap-multilines': require('./lib/rules/jsx-wrap-multilines'),
1515
'self-closing-comp': require('./lib/rules/self-closing-comp'),
1616
'jsx-no-comment-textnodes': require('./lib/rules/jsx-no-comment-textnodes'),
17+
'no-context': require('./lib/rules/no-context'),
1718
'no-danger': require('./lib/rules/no-danger'),
1819
'no-set-state': require('./lib/rules/no-set-state'),
1920
'no-is-mounted': require('./lib/rules/no-is-mounted'),

lib/rules/no-context.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* @fileoverview Prevent usage of context
3+
* @author Zach Guo
4+
*/
5+
'use strict';
6+
7+
var Components = require('../util/Components');
8+
9+
// ------------------------------------------------------------------------------
10+
// Rule Definition
11+
// ------------------------------------------------------------------------------
12+
13+
module.exports = {
14+
meta: {
15+
docs: {
16+
description: 'Prevent usage of context',
17+
category: 'Best Practices',
18+
recommended: true
19+
},
20+
schema: []
21+
},
22+
23+
create: Components.detect(function(context) {
24+
25+
/**
26+
* Checks if we are using context
27+
* @param {ASTNode} node The AST node being checked.
28+
* @returns {Boolean} True if we are using context, false if not.
29+
*/
30+
function isContextUsage(node) {
31+
return Boolean(
32+
node.object.type === 'ThisExpression' &&
33+
node.property.name === 'context'
34+
);
35+
}
36+
37+
/**
38+
* Checks if we are declaring a context
39+
* @param {ASTNode} node The AST node being checked.
40+
* @returns {Boolean} True if we are declaring a context, false if not.
41+
*/
42+
function isContextTypesDeclaration(node) {
43+
// Special case for class properties
44+
// (babel-eslint does not expose property name so we have to rely on tokens)
45+
if (node && node.type === 'ClassProperty') {
46+
var tokens = context.getFirstTokens(node, 2);
47+
if (
48+
tokens[0].value === 'contextTypes' ||
49+
(tokens[1] && tokens[1].value === 'contextTypes')
50+
) {
51+
return true;
52+
}
53+
return false;
54+
}
55+
return Boolean(
56+
node &&
57+
node.name === 'contextTypes'
58+
);
59+
}
60+
61+
/**
62+
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
63+
* FunctionDeclaration, or FunctionExpression
64+
* @returns {Boolean} True if we are using context, false if not.
65+
*/
66+
function isContextUsageInStatelessComponent(node) {
67+
return Boolean(
68+
node &&
69+
node.params &&
70+
node.params[1] &&
71+
node.params[1].name === 'context'
72+
);
73+
}
74+
75+
/**
76+
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
77+
* FunctionDeclaration, or FunctionExpression
78+
*/
79+
function handleStatelessComponent(node) {
80+
if (isContextUsageInStatelessComponent(node)) {
81+
context.report({
82+
node: node,
83+
message: 'Using context is not allowed.'
84+
});
85+
}
86+
}
87+
88+
return {
89+
90+
ClassProperty: function(node) {
91+
if (isContextTypesDeclaration(node)) {
92+
context.report({
93+
node: node,
94+
message: 'Using context is not allowed.'
95+
});
96+
}
97+
},
98+
99+
MemberExpression: function(node) {
100+
if (
101+
isContextUsage(node) ||
102+
isContextTypesDeclaration(node) ||
103+
isContextTypesDeclaration(node.property)
104+
) {
105+
context.report({
106+
node: node,
107+
message: 'Using context is not allowed.'
108+
});
109+
}
110+
},
111+
112+
FunctionDeclaration: handleStatelessComponent,
113+
114+
ArrowFunctionExpression: handleStatelessComponent,
115+
116+
FunctionExpression: handleStatelessComponent
117+
118+
};
119+
120+
})
121+
};

tests/lib/rules/no-context.js

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/**
2+
* @fileoverview Prevent usage of context
3+
* @author Zach Guo
4+
*/
5+
'use strict';
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
var rule = require('../../../lib/rules/no-context');
12+
var RuleTester = require('eslint').RuleTester;
13+
14+
var parserOptions = {
15+
ecmaVersion: 6,
16+
ecmaFeatures: {
17+
jsx: true
18+
}
19+
};
20+
21+
// ------------------------------------------------------------------------------
22+
// Tests
23+
// ------------------------------------------------------------------------------
24+
25+
var ruleTester = new RuleTester();
26+
ruleTester.run('no-context', rule, {
27+
28+
valid: [{
29+
code: [
30+
'var Hello = React.createClass({',
31+
' render: function() {',
32+
' return <div>Hello {this.props.name}</div>;',
33+
' }',
34+
'});'
35+
].join('\n'),
36+
parserOptions: parserOptions
37+
}, {
38+
code: [
39+
'var Hello = function() {',
40+
' var context;',
41+
' return context;',
42+
'};'
43+
].join('\n'),
44+
parserOptions: parserOptions
45+
}],
46+
47+
invalid: [{
48+
code: [
49+
'var Hello = React.createClass({',
50+
' render: function() {',
51+
' return <div>Hello {this.context.name}</div>;',
52+
' }',
53+
'});'
54+
].join('\n'),
55+
parserOptions: parserOptions,
56+
errors: [{
57+
message: 'Using context is not allowed.'
58+
}]
59+
}, {
60+
code: [
61+
'class Hello extends React.Component {',
62+
' render() {',
63+
' return <div>Hello {this.context.name}</div>;',
64+
' }',
65+
'};'
66+
].join('\n'),
67+
parserOptions: parserOptions,
68+
errors: [{
69+
message: 'Using context is not allowed.'
70+
}]
71+
}, {
72+
code: [
73+
'class Hello extends React.Component {',
74+
' render() {',
75+
' return <div>Hello</div>;',
76+
' }',
77+
'};',
78+
'Hello.contextTypes = {',
79+
' name: React.PropTypes.string',
80+
'};'
81+
].join('\n'),
82+
parserOptions: parserOptions,
83+
errors: [{
84+
message: 'Using context is not allowed.'
85+
}]
86+
}, {
87+
code: [
88+
'class Hello extends React.Component {',
89+
' render() {',
90+
' const {name} = this.context;',
91+
' return <div>Hello {name}</div>;',
92+
' }',
93+
'};'
94+
].join('\n'),
95+
parserOptions: parserOptions,
96+
errors: [{
97+
message: 'Using context is not allowed.'
98+
}]
99+
}, {
100+
code: [
101+
'const Hello = (props, context) => {',
102+
' return <div>Hello {context.name}</div>;',
103+
'};'
104+
].join('\n'),
105+
parserOptions: parserOptions,
106+
errors: [{
107+
message: 'Using context is not allowed.'
108+
}]
109+
}, {
110+
code: [
111+
'const Hello = (props, context) => {',
112+
' return <div>Hello {context.name}</div>;',
113+
'};',
114+
'Hello.contextTypes = {',
115+
' name: React.PropTypes.string',
116+
'};'
117+
].join('\n'),
118+
parserOptions: parserOptions,
119+
errors: [{
120+
message: 'Using context is not allowed.'
121+
}, {
122+
message: 'Using context is not allowed.'
123+
}]
124+
}]
125+
});

0 commit comments

Comments
 (0)