38 | ```
39 |
40 | ### `allowArrowFunctions`
41 |
42 | When `true` the following is not considered a warning:
43 |
44 | ```jsx
45 |
alert("1337")} />
46 | ```
47 |
48 | ### `allowBind`
49 |
50 | When `true` the following is not considered a warning:
51 |
52 | ```jsx
53 |
54 | ```
55 |
56 | ## Protips
57 |
58 | ### Lists of Items
59 |
60 | A common use case of `bind` in render is when rendering a list, to have a separate callback per list item:
61 |
62 | ```js
63 | var List = React.createClass({
64 | render() {
65 | return (
66 |
67 | {this.props.items.map(item =>
68 | -
69 | ...
70 |
71 | )}
72 |
73 | );
74 | }
75 | });
76 | ```
77 |
78 | Rather than doing it this way, pull the repeated section into its own component:
79 |
80 | ```js
81 | var List = React.createClass({
82 | render() {
83 | return (
84 |
85 | {this.props.items.map(item =>
86 |
87 | )}
88 |
89 | );
90 | }
91 | });
92 |
93 | var ListItem = React.createClass({
94 | render() {
95 | return (
96 |
97 | ...
98 |
99 | );
100 | },
101 | _onClick() {
102 | this.props.onItemClick(this.props.item.id);
103 | }
104 | });
105 | ```
106 |
107 | This will speed up rendering, as it avoids the need to create new functions (through `bind` calls) on every render.
108 |
109 | ### ES6 Classes
110 |
111 | Unfortunately [React ES6 classes](https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#es6-classes) do not autobind their methods like components created with the older `React.createClass` syntax. There are several approaches to binding methods for ES6 classes. A basic approach is to just manually bind the methods in the constructor:
112 |
113 | ```js
114 | class Foo extends React.Component {
115 | constructor() {
116 | super();
117 | this._onClick = this._onClick.bind(this);
118 | }
119 | render() {
120 | return (
121 |
122 | Hello!
123 |
124 | );
125 | }
126 | _onClick() {
127 | // Do whatever you like, referencing "this" as appropriate
128 | }
129 | }
130 | ```
131 |
132 | A more sophisticated approach would be to use something like an [autobind ES7 decorator](https://www.npmjs.com/package/core-decorators#autobind) or [property initializers](https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding).
133 |
134 | ## When Not To Use It
135 |
136 | If you do not use JSX or do not want to enforce that `bind` or arrow functions are not used in props, then you can disable this rule.
137 |
--------------------------------------------------------------------------------
/lib/rules/require-render-return.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Enforce ES5 or ES6 class for returning value in render function.
3 | * @author Mark Orel
4 | */
5 | 'use strict';
6 |
7 | var Components = require('../util/Components');
8 |
9 | // ------------------------------------------------------------------------------
10 | // Rule Definition
11 | // ------------------------------------------------------------------------------
12 |
13 | module.exports = Components.detect(function(context, components, utils) {
14 |
15 | /**
16 | * Mark a return statement as present
17 | * @param {ASTNode} node The AST node being checked.
18 | */
19 | function markReturnStatementPresent(node) {
20 | components.set(node, {
21 | hasReturnStatement: true
22 | });
23 | }
24 |
25 | /**
26 | * Get properties for a given AST node
27 | * @param {ASTNode} node The AST node being checked.
28 | * @returns {Array} Properties array.
29 | */
30 | function getComponentProperties(node) {
31 | switch (node.type) {
32 | case 'ClassDeclaration':
33 | return node.body.body;
34 | case 'ObjectExpression':
35 | return node.properties;
36 | default:
37 | return [];
38 | }
39 | }
40 |
41 | /**
42 | * Get properties name
43 | * @param {Object} node - Property.
44 | * @returns {String} Property name.
45 | */
46 | function getPropertyName(node) {
47 | // Special case for class properties
48 | // (babel-eslint does not expose property name so we have to rely on tokens)
49 | if (node.type === 'ClassProperty') {
50 | var tokens = context.getFirstTokens(node, 2);
51 | return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value;
52 | } else if (['MethodDefinition', 'Property'].indexOf(node.type) !== -1) {
53 | return node.key.name;
54 | }
55 | return '';
56 | }
57 |
58 | /**
59 | * Check if a given AST node has a render method
60 | * @param {ASTNode} node The AST node being checked.
61 | * @returns {Boolean} True if there is a render method, false if not
62 | */
63 | function hasRenderMethod(node) {
64 | var properties = getComponentProperties(node);
65 | for (var i = 0, j = properties.length; i < j; i++) {
66 | if (getPropertyName(properties[i]) !== 'render') {
67 | continue;
68 | }
69 | return /FunctionExpression$/.test(properties[i].value.type);
70 | }
71 | return false;
72 | }
73 |
74 | return {
75 | ReturnStatement: function(node) {
76 | var ancestors = context.getAncestors(node).reverse();
77 | var depth = 0;
78 | for (var i = 0, j = ancestors.length; i < j; i++) {
79 | if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) {
80 | depth++;
81 | }
82 | if (
83 | !/(MethodDefinition|(Class)?Property)$/.test(ancestors[i].type) ||
84 | getPropertyName(ancestors[i]) !== 'render' ||
85 | depth > 1
86 | ) {
87 | continue;
88 | }
89 | markReturnStatementPresent(node);
90 | }
91 | },
92 |
93 | ArrowFunctionExpression: function(node) {
94 | if (node.expression === false || getPropertyName(node.parent) !== 'render') {
95 | return;
96 | }
97 | markReturnStatementPresent(node);
98 | },
99 |
100 | 'Program:exit': function() {
101 | var list = components.list();
102 | for (var component in list) {
103 | if (
104 | !list.hasOwnProperty(component) ||
105 | !hasRenderMethod(list[component].node) ||
106 | list[component].hasReturnStatement ||
107 | (!utils.isES5Component(list[component].node) && !utils.isES6Component(list[component].node))
108 | ) {
109 | continue;
110 | }
111 | context.report({
112 | node: list[component].node,
113 | message: 'Your render method should have return statement'
114 | });
115 | }
116 | }
117 | };
118 | });
119 |
120 | module.exports.schema = [{}];
121 |
--------------------------------------------------------------------------------
/lib/rules/forbid-prop-types.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Forbid certain propTypes
3 | */
4 | 'use strict';
5 |
6 | // ------------------------------------------------------------------------------
7 | // Constants
8 | // ------------------------------------------------------------------------------
9 |
10 | var DEFAULTS = ['any', 'array', 'object'];
11 |
12 | // ------------------------------------------------------------------------------
13 | // Rule Definition
14 | // ------------------------------------------------------------------------------
15 |
16 | module.exports = function(context) {
17 |
18 | function isForbidden(type) {
19 | var configuration = context.options[0] || {};
20 |
21 | var forbid = configuration.forbid || DEFAULTS;
22 | return forbid.indexOf(type) >= 0;
23 | }
24 |
25 | /**
26 | * Checks if node is `propTypes` declaration
27 | * @param {ASTNode} node The AST node being checked.
28 | * @returns {Boolean} True if node is `propTypes` declaration, false if not.
29 | */
30 | function isPropTypesDeclaration(node) {
31 |
32 | // Special case for class properties
33 | // (babel-eslint does not expose property name so we have to rely on tokens)
34 | if (node.type === 'ClassProperty') {
35 | var tokens = context.getFirstTokens(node, 2);
36 | if (tokens[0].value === 'propTypes' || (tokens[1] && tokens[1].value === 'propTypes')) {
37 | return true;
38 | }
39 | return false;
40 | }
41 |
42 | return Boolean(
43 | node &&
44 | node.name === 'propTypes'
45 | );
46 | }
47 |
48 |
49 | /**
50 | * Checks if propTypes declarations are forbidden
51 | * @param {Array} declarations The array of AST nodes being checked.
52 | * @returns {void}
53 | */
54 | function checkForbidden(declarations) {
55 | declarations.forEach(function(declaration) {
56 | if (declaration.type !== 'Property') {
57 | return;
58 | }
59 | var target;
60 | var value = declaration.value;
61 | if (
62 | value.type === 'MemberExpression' &&
63 | value.property &&
64 | value.property.name &&
65 | value.property.name === 'isRequired'
66 | ) {
67 | value = value.object;
68 | }
69 | if (
70 | value.type === 'CallExpression' &&
71 | value.callee.type === 'MemberExpression'
72 | ) {
73 | value = value.callee;
74 | }
75 | if (value.property) {
76 | target = value.property.name;
77 | } else if (value.type === 'Identifier') {
78 | target = value.name;
79 | }
80 | if (isForbidden(target)) {
81 | context.report({
82 | node: declaration,
83 | message: 'Prop type `' + target + '` is forbidden'
84 | });
85 | }
86 | });
87 | }
88 |
89 | return {
90 | ClassProperty: function(node) {
91 | if (isPropTypesDeclaration(node) && node.value && node.value.type === 'ObjectExpression') {
92 | checkForbidden(node.value.properties);
93 | }
94 | },
95 |
96 | MemberExpression: function(node) {
97 | if (isPropTypesDeclaration(node.property)) {
98 | var right = node.parent.right;
99 | if (right && right.type === 'ObjectExpression') {
100 | checkForbidden(right.properties);
101 | }
102 | }
103 | },
104 |
105 | ObjectExpression: function(node) {
106 | node.properties.forEach(function(property) {
107 | if (!property.key) {
108 | return;
109 | }
110 |
111 | if (!isPropTypesDeclaration(property.key)) {
112 | return;
113 | }
114 | if (property.value.type === 'ObjectExpression') {
115 | checkForbidden(property.value.properties);
116 | }
117 | });
118 | }
119 |
120 | };
121 | };
122 |
123 | module.exports.schema = [{
124 | type: 'object',
125 | properties: {
126 | forbid: {
127 | type: 'array',
128 | items: {
129 | type: 'string'
130 | }
131 | }
132 | },
133 | additionalProperties: true
134 | }];
135 |
--------------------------------------------------------------------------------
/tests/lib/rules/no-unknown-property.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Tests for no-unknown-property
3 | * @author Yannick Croissant
4 | */
5 |
6 | 'use strict';
7 |
8 | // -----------------------------------------------------------------------------
9 | // Requirements
10 | // -----------------------------------------------------------------------------
11 |
12 | var rule = require('../../../lib/rules/no-unknown-property');
13 | var RuleTester = require('eslint').RuleTester;
14 |
15 | var parserOptions = {
16 | ecmaVersion: 6,
17 | ecmaFeatures: {
18 | jsx: true
19 | }
20 | };
21 |
22 | // -----------------------------------------------------------------------------
23 | // Tests
24 | // -----------------------------------------------------------------------------
25 |
26 | var ruleTester = new RuleTester();
27 | ruleTester.run('no-unknown-property', rule, {
28 | valid: [
29 | {code: '
;', parserOptions: parserOptions},
30 | {code: '
;', parserOptions: parserOptions},
31 | {code: '
;', parserOptions: parserOptions},
32 | {code: '
;', parserOptions: parserOptions},
33 | {code: '
;', parserOptions: parserOptions},
34 | {code: '
;', parserOptions: parserOptions},
35 | {code: '
;', parserOptions: parserOptions},
36 | {code: '
;', parserOptions: parserOptions},
37 | {code: '
;', parserOptions: parserOptions},
38 | {code: '
;', parserOptions: parserOptions},
39 | {code: '
;', parserOptions: parserOptions},
40 | {code: '
;', parserOptions: parserOptions},
41 | {code: '
;', parserOptions: parserOptions}
42 | ],
43 | invalid: [{
44 | code: '
;',
45 | output: '
;',
46 | errors: [{message: 'Unknown property \'class\' found, use \'className\' instead'}],
47 | parserOptions: parserOptions
48 | }, {
49 | code: '
;',
50 | output: '
;',
51 | errors: [{message: 'Unknown property \'for\' found, use \'htmlFor\' instead'}],
52 | parserOptions: parserOptions
53 | }, {
54 | code: '
;',
55 | output: '
;',
56 | errors: [{message: 'Unknown property \'accept-charset\' found, use \'acceptCharset\' instead'}],
57 | parserOptions: parserOptions
58 | }, {
59 | code: '
;',
60 | output: '
;',
61 | errors: [{message: 'Unknown property \'http-equiv\' found, use \'httpEquiv\' instead'}],
62 | parserOptions: parserOptions
63 | }, {
64 | code: '
;',
65 | output: '
;',
66 | errors: [{message: 'Unknown property \'accesskey\' found, use \'accessKey\' instead'}],
67 | parserOptions: parserOptions
68 | }, {
69 | code: '
;',
70 | output: '
;',
71 | errors: [{message: 'Unknown property \'onclick\' found, use \'onClick\' instead'}],
72 | parserOptions: parserOptions
73 | }, {
74 | code: '
;',
75 | output: '
;',
76 | errors: [{message: 'Unknown property \'onmousedown\' found, use \'onMouseDown\' instead'}],
77 | parserOptions: parserOptions
78 | }, {
79 | code: '
;',
80 | output: '
;',
81 | errors: [{message: 'Unknown property \'xlink:href\' found, use \'xlinkHref\' instead'}],
82 | parserOptions: parserOptions,
83 | settings: {
84 | react: {
85 | version: '0.14.0'
86 | }
87 | }
88 | }, {
89 | code: '
;',
90 | output: '
;',
91 | errors: [{message: 'Unknown property \'clip-path\' found, use \'clipPath\' instead'}],
92 | parserOptions: parserOptions,
93 | settings: {
94 | react: {
95 | version: '0.14.0'
96 | }
97 | }
98 | }]
99 | });
100 |
--------------------------------------------------------------------------------
/tests/lib/rules/no-direct-mutation-state.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Prevent direct mutation of this.state
3 | * @author David Petersen
4 | */
5 | 'use strict';
6 |
7 | // ------------------------------------------------------------------------------
8 | // Requirements
9 | // ------------------------------------------------------------------------------
10 |
11 | var rule = require('../../../lib/rules/no-direct-mutation-state');
12 | var RuleTester = require('eslint').RuleTester;
13 |
14 | var parserOptions = {
15 | ecmaVersion: 6,
16 | ecmaFeatures: {
17 | jsx: true
18 | }
19 | };
20 |
21 | require('babel-eslint');
22 |
23 | // ------------------------------------------------------------------------------
24 | // Tests
25 | // ------------------------------------------------------------------------------
26 |
27 | var ruleTester = new RuleTester();
28 | ruleTester.run('no-direct-mutation-state', rule, {
29 |
30 | valid: [{
31 | code: [
32 | 'var Hello = React.createClass({',
33 | ' render: function() {',
34 | ' return
Hello {this.props.name}
;',
35 | ' }',
36 | '});'
37 | ].join('\n'),
38 | parserOptions: parserOptions
39 | }, {
40 | code: [
41 | 'var Hello = React.createClass({',
42 | ' render: function() {',
43 | ' var obj = {state: {}};',
44 | ' obj.state.name = "foo";',
45 | ' return
Hello {obj.state.name}
;',
46 | ' }',
47 | '});'
48 | ].join('\n'),
49 | parserOptions: parserOptions
50 | }, {
51 | code: [
52 | 'var Hello = "foo";',
53 | 'module.exports = {};'
54 | ].join('\n'),
55 | parserOptions: parserOptions
56 | }, {
57 | code: [
58 | 'class Hello {',
59 | ' getFoo() {',
60 | ' this.state.foo = \'bar\'',
61 | ' return this.state.foo;',
62 | ' }',
63 | '}'
64 | ].join('\n'),
65 | parserOptions: parserOptions
66 | }],
67 |
68 | invalid: [{
69 | code: [
70 | 'var Hello = React.createClass({',
71 | ' render: function() {',
72 | ' this.state.foo = "bar"',
73 | ' return
Hello {this.props.name}
;',
74 | ' }',
75 | '});'
76 | ].join('\n'),
77 | parserOptions: parserOptions,
78 | errors: [{
79 | message: 'Do not mutate state directly. Use setState().'
80 | }]
81 | }, {
82 | code: [
83 | 'var Hello = React.createClass({',
84 | ' render: function() {',
85 | ' this.state.person.name= "bar"',
86 | ' return
Hello {this.props.name}
;',
87 | ' }',
88 | '});'
89 | ].join('\n'),
90 | parserOptions: parserOptions,
91 | errors: [{
92 | message: 'Do not mutate state directly. Use setState().'
93 | }]
94 | }, {
95 | code: [
96 | 'var Hello = React.createClass({',
97 | ' render: function() {',
98 | ' this.state.person.name.first = "bar"',
99 | ' return
Hello
;',
100 | ' }',
101 | '});'
102 | ].join('\n'),
103 | parserOptions: parserOptions,
104 | errors: [{
105 | message: 'Do not mutate state directly. Use setState().'
106 | }]
107 | }, {
108 | code: [
109 | 'var Hello = React.createClass({',
110 | ' render: function() {',
111 | ' this.state.person.name.first = "bar"',
112 | ' this.state.person.name.last = "baz"',
113 | ' return
Hello
;',
114 | ' }',
115 | '});'
116 | ].join('\n'),
117 | parserOptions: parserOptions,
118 | errors: [{
119 | message: 'Do not mutate state directly. Use setState().',
120 | line: 3,
121 | column: 5
122 | }, {
123 | message: 'Do not mutate state directly. Use setState().',
124 | line: 4,
125 | column: 5
126 | }]
127 | }
128 | /**
129 | * Would be nice to prevent this too
130 | , {
131 | code: [
132 | 'var Hello = React.createClass({',
133 | ' render: function() {',
134 | ' var that = this;',
135 | ' that.state.person.name.first = "bar"',
136 | ' return
Hello
;',
137 | ' }',
138 | '});'
139 | ].join('\n'),
140 | parserOptions: parserOptions,
141 | errors: [{
142 | message: 'Do not mutate state directly. Use setState().'
143 | }]
144 | }*/
145 | ]
146 | });
147 |
--------------------------------------------------------------------------------