Skip to content

Add no-context rule (fixes #455) #736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#

* [react/display-name](docs/rules/display-name.md): Prevent missing `displayName` in a React component definition
* [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes
* [react/no-context](docs/rules/no-context.md): Prevent usage of `context`
* [react/no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties
* [react/no-deprecated](docs/rules/no-deprecated.md): Prevent usage of deprecated methods
* [react/no-did-mount-set-state](docs/rules/no-did-mount-set-state.md): Prevent usage of `setState` in `componentDidMount`
Expand Down
59 changes: 59 additions & 0 deletions docs/rules/no-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Prevent usage of context (no-context)

[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. Consider higher order components or Flux architecture instead.

## Rule Details

The following patterns are considered warnings:

```jsx
class Button extends React.Component {
render() {
return (
<button style={{background: this.context.color}}>
{this.props.children}
</button>
);
}
}
Button.contextTypes = {
color: React.PropTypes.string
};
```

```jsx
const Button = (props, context) =>
<button style={{background: context.color}}>
{props.children}
</button>;
Button.contextTypes = {
color: React.PropTypes.string
};
```

The following patterns are not considered warnings:

```jsx
class Button extends React.Component {
render() {
return (
<button style={{background: this.props.color}}>
{this.props.children}
</button>
);
}
}
Button.propTypes = {
color: React.PropTypes.string
};
```

```jsx
const Button = (props) =>
<button style={{background: props.color}}>
{props.children}
</button>;
Button.propTypes = {
color: React.PropTypes.string
};
```
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var rules = {
'jsx-wrap-multilines': require('./lib/rules/jsx-wrap-multilines'),
'self-closing-comp': require('./lib/rules/self-closing-comp'),
'jsx-no-comment-textnodes': require('./lib/rules/jsx-no-comment-textnodes'),
'no-context': require('./lib/rules/no-context'),
'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'),
Expand Down
151 changes: 151 additions & 0 deletions lib/rules/no-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/**
* @fileoverview Prevent usage of context
* @author Zach Guo
*/
'use strict';

var Components = require('../util/Components');

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: 'Prevent usage of context',
category: 'Best Practices',
recommended: true
},
schema: []
},

create: Components.detect(function(context, component, utils) {

/**
* Checks if we are using context
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if we are using context, false if not.
*/
function isContextUsage(node) {
return Boolean(
(utils.getParentES6Component() || utils.getParentES5Component()) &&
node.object.type === 'ThisExpression' &&
node.property.name === 'context'
);
}

/**
* Checks if we are declaring a context
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if we are declaring a context, false if not.
*/
function isContextTypesDeclaration(node) {
// Special case for class properties
// (babel-eslint does not expose property name so we have to rely on tokens)
if (node && node.type === 'ClassProperty') {
var tokens = context.getFirstTokens(node, 2);
if (
tokens[0].value === 'contextTypes' ||
(tokens[1] && tokens[1].value === 'contextTypes')
) {
return true;
}
return false;
}
return Boolean(
node &&
node.name === 'contextTypes'
);
}

/**
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
* FunctionDeclaration, or FunctionExpression
* @returns {Boolean} True if node is a stateless functional component,
* false if not.
*/
function isStatelessComponent(node) {
return Boolean(
node.parent.type === 'VariableDeclarator' &&
utils.isReturningJSX(node)
);
}

/**
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
* FunctionDeclaration, or FunctionExpression
* @returns {Boolean} True if we are using context, false if not.
*/
function isContextUsageInStatelessComponent(node) {
return Boolean(
node &&
node.params &&
node.params[1]
);
}

/**
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
* FunctionDeclaration, or FunctionExpression
*/
function handleStatelessComponent(node) {
if (
isStatelessComponent(node) &&
isContextUsageInStatelessComponent(node)
) {
context.report({
node: node,
message: 'Using context is not allowed.'
});
}
}

return {

ClassProperty: function(node) {
if (isContextTypesDeclaration(node)) {
context.report({
node: node,
message: 'Using context is not allowed.'
});
}
},

MemberExpression: function(node) {
if (
isContextUsage(node) ||
isContextTypesDeclaration(node) ||
isContextTypesDeclaration(node.property)
) {
context.report({
node: node,
message: 'Using context is not allowed.'
});
}
},

ObjectExpression: function(node) {
node.properties.forEach(function(property) {
if (!isContextTypesDeclaration(property.key)) {
return;
}
if (property.value.type === 'ObjectExpression') {
context.report({
node: node,
message: 'Using context is not allowed.'
});
}
});
},

FunctionDeclaration: handleStatelessComponent,

ArrowFunctionExpression: handleStatelessComponent,

FunctionExpression: handleStatelessComponent

};

})
};
Loading