├── .eslintignore ├── .travis.yml ├── .gitignore ├── .editorconfig ├── appveyor.yml ├── docs └── rules │ ├── jsx-no-undef.md │ ├── self-closing-comp.md │ ├── jsx-no-literals.md │ ├── jsx-no-duplicate-props.md │ ├── no-unknown-property.md │ ├── jsx-key.md │ ├── jsx-uses-vars.md │ ├── require-render-return.md │ ├── no-danger.md │ ├── no-is-mounted.md │ ├── jsx-no-target-blank.md │ ├── no-direct-mutation-state.md │ ├── react-in-jsx-scope.md │ ├── no-set-state.md │ ├── no-string-refs.md │ ├── jsx-handler-names.md │ ├── no-deprecated.md │ ├── jsx-boolean-value.md │ ├── jsx-pascal-case.md │ ├── wrap-multilines.md │ ├── prefer-es6-class.md │ ├── jsx-indent.md │ ├── jsx-space-before-closing.md │ ├── jsx-max-props-per-line.md │ ├── require-extension.md │ ├── jsx-indent-props.md │ ├── prefer-stateless-function.md │ ├── forbid-prop-types.md │ ├── jsx-equals-spacing.md │ ├── jsx-uses-react.md │ ├── no-multi-comp.md │ ├── jsx-first-prop-new-line.md │ ├── jsx-sort-props.md │ ├── no-did-update-set-state.md │ ├── no-did-mount-set-state.md │ ├── display-name.md │ ├── jsx-curly-spacing.md │ ├── sort-prop-types.md │ ├── prop-types.md │ └── jsx-no-bind.md ├── tests ├── index.js └── lib │ └── rules │ ├── no-danger.js │ ├── jsx-boolean-value.js │ ├── jsx-no-target-blank.js │ ├── jsx-uses-react.js │ ├── self-closing-comp.js │ ├── jsx-pascal-case.js │ ├── jsx-no-duplicate-props.js │ ├── jsx-key.js │ ├── jsx-max-props-per-line.js │ ├── jsx-no-undef.js │ ├── react-in-jsx-scope.js │ ├── require-extension.js │ ├── jsx-indent-props.js │ ├── no-is-mounted.js │ ├── jsx-handler-names.js │ ├── no-set-state.js │ ├── no-string-refs.js │ ├── prefer-es6-class.js │ ├── jsx-no-bind.js │ ├── no-deprecated.js │ ├── jsx-first-prop-new-line.js │ ├── jsx-space-before-closing.js │ ├── no-unknown-property.js │ └── no-direct-mutation-state.js ├── lib ├── rules │ ├── jsx-uses-vars.js │ ├── jsx-uses-react.js │ ├── react-in-jsx-scope.js │ ├── prefer-es6-class.js │ ├── jsx-no-target-blank.js │ ├── jsx-no-literals.js │ ├── no-set-state.js │ ├── no-is-mounted.js │ ├── jsx-sort-prop-types.js │ ├── jsx-no-duplicate-props.js │ ├── self-closing-comp.js │ ├── jsx-first-prop-new-line.js │ ├── jsx-boolean-value.js │ ├── jsx-max-props-per-line.js │ ├── no-did-mount-set-state.js │ ├── no-did-update-set-state.js │ ├── jsx-no-bind.js │ ├── no-multi-comp.js │ ├── no-danger.js │ ├── jsx-pascal-case.js │ ├── jsx-handler-names.js │ ├── require-extension.js │ ├── jsx-key.js │ ├── no-direct-mutation-state.js │ ├── jsx-space-before-closing.js │ ├── jsx-no-undef.js │ ├── wrap-multilines.js │ ├── no-string-refs.js │ ├── jsx-equals-spacing.js │ ├── jsx-sort-props.js │ ├── require-render-return.js │ └── forbid-prop-types.js └── util │ ├── pragma.js │ ├── version.js │ └── variable.js ├── LICENSE ├── package.json └── index.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | reports/** 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | - 0.12 5 | - iojs 6 | - 4 7 | - 5 8 | after_success: 9 | - npm run coveralls 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.sublime-project 10 | *.sublime-workspace 11 | pids 12 | logs 13 | reports 14 | build 15 | node_modules 16 | npm-debug.log 17 | sftp-config.json 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '0.10' 4 | - nodejs_version: '0.12' 5 | - nodejs_version: '3' 6 | - nodejs_version: '4' 7 | - nodejs_version: '5' 8 | install: 9 | - ps: Install-Product node $env:nodejs_version 10 | - set CI=true 11 | - npm -g install npm@latest 12 | - set PATH=%APPDATA%\npm;%PATH% 13 | - npm install 14 | matrix: 15 | fast_finish: true 16 | build: off 17 | version: '{build}' 18 | shallow_clone: true 19 | clone_depth: 1 20 | test_script: 21 | - node --version 22 | - npm --version 23 | - npm test 24 | -------------------------------------------------------------------------------- /docs/rules/jsx-no-undef.md: -------------------------------------------------------------------------------- 1 | # Disallow undeclared variables in JSX (jsx-no-undef) 2 | 3 | This rule helps locate potential ReferenceErrors resulting from misspellings or missing components. 4 | 5 | ## Rule Details 6 | 7 | The following patterns are considered warnings: 8 | 9 | ```js 10 | ; 11 | ``` 12 | 13 | The following patterns are not considered warnings: 14 | 15 | ```js 16 | var Hello = require('./Hello'); 17 | 18 | ; 19 | ``` 20 | 21 | ## When Not To Use It 22 | 23 | If you are not using JSX then you can disable this rule. 24 | -------------------------------------------------------------------------------- /docs/rules/self-closing-comp.md: -------------------------------------------------------------------------------- 1 | # Prevent extra closing tags for components without children (self-closing-comp) 2 | 3 | Components without children can be self-closed to avoid unnecessary extra closing tag. 4 | 5 | ## Rule Details 6 | 7 | The following patterns are considered warnings: 8 | 9 | ```js 10 | var HelloJohn = ; 11 | ``` 12 | 13 | The following patterns are not considered warnings: 14 | 15 | ```js 16 | var contentContainer =
; 17 | 18 | var HelloJohn = ; 19 | 20 | var Profile = ; 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/rules/jsx-no-literals.md: -------------------------------------------------------------------------------- 1 | # Prevent usage of unwrapped JSX strings (jsx-no-literals) 2 | 3 | In JSX when using a literal string you can wrap it in a JSX container `{'TEXT'}`. 4 | This rules requires that you wrap all literal strings. 5 | Prevents any odd artifacts of highlighters if your unwrapped string contains an enclosing character like `'` in contractions and enforces consistency. 6 | 7 | ## Rule Details 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```javascript 12 | var Hello =
test
; 13 | ``` 14 | 15 | The following patterns are not considered warnings: 16 | 17 | ```javascript 18 | var Hello =
{'test'}
; 19 | ``` 20 | 21 | ## When Not To Use It 22 | 23 | If you do not want to enforce any style JSX literals, then you can disable this rule. 24 | -------------------------------------------------------------------------------- /docs/rules/jsx-no-duplicate-props.md: -------------------------------------------------------------------------------- 1 | # Prevent duplicate properties in JSX (jsx-no-duplicate-props) 2 | 3 | Creating JSX elements with duplicate props can cause unexpected behavior in your application. 4 | 5 | ## Rule Details 6 | 7 | The following patterns are considered warnings: 8 | 9 | ```js 10 | ; 11 | ``` 12 | 13 | The following patterns are not considered warnings: 14 | 15 | ```js 16 | ; 17 | ``` 18 | 19 | ## Rule Options 20 | 21 | ```js 22 | ... 23 | "jsx-no-duplicate-props": [, { "ignoreCase": }] 24 | ... 25 | ``` 26 | 27 | ### `ignoreCase` 28 | 29 | When `true` the rule ignores the case of the props. Default to `false`. 30 | 31 | ## When Not To Use It 32 | 33 | If you are not using JSX then you can disable this rule. 34 | -------------------------------------------------------------------------------- /docs/rules/no-unknown-property.md: -------------------------------------------------------------------------------- 1 | # Prevent usage of unknown DOM property (no-unknown-property) 2 | 3 | In JSX all DOM properties and attributes should be camelCased to be consistent with standard JavaScript style. This can be a possible source of error if you are used to writing plain HTML. 4 | 5 | **Fixable:** This rule is automatically fixable using the `--fix` flag on the command line. 6 | 7 | ## Rule Details 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```js 12 | var React = require('react'); 13 | 14 | var Hello =
Hello World
; 15 | ``` 16 | 17 | The following patterns are not considered warnings: 18 | 19 | ```js 20 | var React = require('react'); 21 | 22 | var Hello =
Hello World
; 23 | ``` 24 | 25 | ## When Not To Use It 26 | 27 | If you are not using JSX you can disable this rule. 28 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict'; 3 | 4 | var plugin = require('..'); 5 | 6 | var assert = require('assert'); 7 | var fs = require('fs'); 8 | var path = require('path'); 9 | 10 | var rules = fs.readdirSync(path.resolve(__dirname, '../lib/rules/')) 11 | .map(function(f) { 12 | return path.basename(f, '.js'); 13 | }); 14 | 15 | describe('all rule files should be exported by the plugin', function() { 16 | rules.forEach(function(ruleName) { 17 | it('should export ' + ruleName, function() { 18 | assert.equal( 19 | plugin.rules[ruleName], 20 | require(path.join('../lib/rules', ruleName)) 21 | ); 22 | }); 23 | }); 24 | }); 25 | 26 | describe('configurations', function() { 27 | it('should export a \'recommended\' configuration', function() { 28 | assert(plugin.configs.recommended); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /docs/rules/jsx-key.md: -------------------------------------------------------------------------------- 1 | # Detect missing `key` prop (jsx-key) 2 | 3 | Warn if an element that likely requires a `key` prop--namely, one present in an 4 | array literal or an arrow function expression. 5 | 6 | ## Rule Details 7 | 8 | The following patterns are considered warnings: 9 | 10 | ```jsx 11 | [, , ]; 12 | 13 | data.map(x => x); 14 | ``` 15 | 16 | The following patterns are not considered warnings: 17 | 18 | ```jsx 19 | [, , ]; 20 | 21 | data.map((x, i) => x); 22 | ``` 23 | 24 | ## When not to use 25 | 26 | If you are not using JSX then you can disable this rule. 27 | 28 | Also, if you have some prevalent situation where you use arrow functions to 29 | return JSX that will not be held in an iterable, you may want to disable this 30 | rule. 31 | -------------------------------------------------------------------------------- /docs/rules/jsx-uses-vars.md: -------------------------------------------------------------------------------- 1 | # Prevent variables used in JSX to be incorrectly marked as unused (jsx-uses-vars) 2 | 3 | Since 0.17.0 the ESLint `no-unused-vars` rule does not detect variables used in JSX ([see details](http://eslint.org/blog/2015/03/eslint-0.17.0-released#changes-to-jsxreact-handling)). This rule will find variables used in JSX and mark them as used. 4 | 5 | This rule only has an effect when the `no-unused-vars` rule is enabled. 6 | 7 | ## Rule Details 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```js 12 | var Hello = require('./Hello'); 13 | ``` 14 | 15 | The following patterns are not considered warnings: 16 | 17 | ```js 18 | var Hello = require('./Hello'); 19 | 20 | ; 21 | ``` 22 | 23 | ## When Not To Use It 24 | 25 | If you are not using JSX or if you do not use the `no-unused-vars` rule then you can disable this rule. 26 | -------------------------------------------------------------------------------- /docs/rules/require-render-return.md: -------------------------------------------------------------------------------- 1 | # Enforce ES5 or ES6 class for returning value in render function (require-render-return) 2 | 3 | When writing the `render` method in a component it is easy to forget to return the JSX content. This rule will warn if the `return` statement is missing. 4 | 5 | ## Rule Details 6 | 7 | The following patterns are considered warnings: 8 | 9 | ```js 10 | var Hello = React.createClass({ 11 | render() { 12 |
Hello
; 13 | } 14 | }); 15 | 16 | class Hello extends React.Component { 17 | render() { 18 |
Hello
; 19 | } 20 | } 21 | ``` 22 | 23 | The following patterns are not considered warnings: 24 | 25 | ```js 26 | var Hello = React.createClass({ 27 | render() { 28 | return
Hello
; 29 | } 30 | }); 31 | 32 | class Hello extends React.Component { 33 | render() { 34 | return
Hello
; 35 | } 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/rules/no-danger.md: -------------------------------------------------------------------------------- 1 | # Prevent usage of dangerous JSX properties (no-danger) 2 | 3 | Dangerous properties in React are those whose behavior is known to be a common source of application vulnerabilities. The properties names clearly indicate they are dangerous and should be avoided unless great care is taken. 4 | 5 | See https://facebook.github.io/react/tips/dangerously-set-inner-html.html 6 | 7 | ## Rule Details 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```js 12 | var React = require('react'); 13 | 14 | var Hello =
; 15 | ``` 16 | 17 | The following patterns are not considered warnings: 18 | 19 | ```js 20 | var React = require('react'); 21 | 22 | var Hello =
Hello World
; 23 | ``` 24 | 25 | ## When Not To Use It 26 | 27 | If you are certain the content passed to dangerouslySetInnerHTML is sanitized HTML you can disable this rule. 28 | -------------------------------------------------------------------------------- /docs/rules/no-is-mounted.md: -------------------------------------------------------------------------------- 1 | # Prevent usage of isMounted (no-is-mounted) 2 | 3 | [`isMounted` is an anti-pattern][anti-pattern], is not available when using ES6 classes, and it is on its way to being officially deprecated. 4 | 5 | [anti-pattern]: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html 6 | 7 | ## Rule Details 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```js 12 | var Hello = React.createClass({ 13 | handleClick: function() { 14 | setTimeout(function() { 15 | if (this.isMounted()) { 16 | return; 17 | } 18 | }); 19 | }, 20 | render: function() { 21 | return
Hello
; 22 | } 23 | }); 24 | ``` 25 | 26 | The following patterns are not considered warnings: 27 | 28 | ```js 29 | var Hello = React.createClass({ 30 | render: function() { 31 | return
Hello
; 32 | } 33 | }); 34 | ``` 35 | -------------------------------------------------------------------------------- /lib/rules/jsx-uses-vars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent variables used in JSX to be marked as unused 3 | * @author Yannick Croissant 4 | */ 5 | 'use strict'; 6 | 7 | var variableUtil = require('../util/variable'); 8 | 9 | // ------------------------------------------------------------------------------ 10 | // Rule Definition 11 | // ------------------------------------------------------------------------------ 12 | 13 | module.exports = function(context) { 14 | 15 | return { 16 | JSXExpressionContainer: function(node) { 17 | if (node.expression.type !== 'Identifier') { 18 | return; 19 | } 20 | variableUtil.markVariableAsUsed(context, node.expression.name); 21 | }, 22 | 23 | JSXIdentifier: function(node) { 24 | if (node.parent.type === 'JSXAttribute') { 25 | return; 26 | } 27 | variableUtil.markVariableAsUsed(context, node.name); 28 | } 29 | 30 | }; 31 | 32 | }; 33 | 34 | module.exports.schema = []; 35 | -------------------------------------------------------------------------------- /docs/rules/jsx-no-target-blank.md: -------------------------------------------------------------------------------- 1 | # Prevent usage of unsafe target='_blank' (jsx-no-target-blank) 2 | 3 | When creating a JSX element that has an a tag, it is often desired to have 4 | the link open in a new tab using the target='_blank' attribute. Using this 5 | attribute unaccompanied by rel='noreferrer noopener', however, is a severe 6 | security vulnerability ([see here for more details](https://mathiasbynens.github.io/rel-noopener)) 7 | This rules requires that you accompany all target='_blank' attributes with rel='noreferrer noopener'. 8 | 9 | ## Rule Details 10 | 11 | The following patterns are considered errors: 12 | 13 | ```javascript 14 | var Hello = 15 | ``` 16 | 17 | The following patterns are not considered erros: 18 | 19 | ```javascript 20 | var Hello =

21 | var Hello = 22 | var Hello = 23 | ``` 24 | 25 | ## When Not To Use It 26 | 27 | If you do not have any external links, you can disable this rule 28 | -------------------------------------------------------------------------------- /lib/util/pragma.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Utility functions for React pragma configuration 3 | * @author Yannick Croissant 4 | */ 5 | 'use strict'; 6 | 7 | var JSX_ANNOTATION_REGEX = /^\*\s*@jsx\s+([^\s]+)/; 8 | 9 | function getFromContext(context) { 10 | var pragma = 'React'; 11 | // .eslintrc shared settings (http://eslint.org/docs/user-guide/configuring#adding-shared-settings) 12 | if (context.settings.react && context.settings.react.pragma) { 13 | pragma = context.settings.react.pragma; 14 | // Deprecated pragma option, here for backward compatibility 15 | } else if (context.options[0] && context.options[0].pragma) { 16 | pragma = context.options[0].pragma; 17 | } 18 | return pragma.split('.')[0]; 19 | } 20 | 21 | function getFromNode(node) { 22 | var matches = JSX_ANNOTATION_REGEX.exec(node.value); 23 | if (!matches) { 24 | return false; 25 | } 26 | return matches[1].split('.')[0]; 27 | } 28 | 29 | module.exports = { 30 | getFromContext: getFromContext, 31 | getFromNode: getFromNode 32 | }; 33 | -------------------------------------------------------------------------------- /docs/rules/no-direct-mutation-state.md: -------------------------------------------------------------------------------- 1 | # Prevent direct mutation of this.state (no-direct-mutation-state) 2 | 3 | NEVER mutate `this.state` directly, as calling `setState()` afterwards may replace 4 | the mutation you made. Treat `this.state` as if it were immutable. 5 | 6 | ## Rule Details 7 | 8 | This rule is aimed to forbid the use of mutating `this.state` directly. 9 | 10 | The following patterns are considered warnings: 11 | 12 | ```js 13 | var Hello = React.createClass({ 14 | componentDidMount: function() { 15 | this.state.name = this.props.name.toUpperCase(); 16 | }, 17 | render: function() { 18 | return
Hello {this.state.name}
; 19 | } 20 | }); 21 | ``` 22 | 23 | 24 | The following patterns are not considered warnings: 25 | 26 | ```js 27 | var Hello = React.createClass({ 28 | componentDidMount: function() { 29 | this.setState({ 30 | name: this.props.name.toUpperCase(); 31 | }); 32 | }, 33 | render: function() { 34 | return
Hello {this.state.name}
; 35 | } 36 | }); 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/rules/react-in-jsx-scope.md: -------------------------------------------------------------------------------- 1 | # Prevent missing React when using JSX (react-in-jsx-scope) 2 | 3 | When using JSX, `` expands to `React.createElement("a")`. Therefore the 4 | `React` variable must be in scope. 5 | 6 | If you are using the @jsx pragma this rule will check the designated variable and not the `React` one. 7 | 8 | ## Rule Details 9 | 10 | The following patterns are considered warnings: 11 | 12 | ```js 13 | var Hello =
Hello {this.props.name}
; 14 | ``` 15 | 16 | ```js 17 | /** @jsx Foo.bar */ 18 | var React = require('react'); 19 | 20 | var Hello =
Hello {this.props.name}
; 21 | ``` 22 | 23 | The following patterns are not considered warnings: 24 | 25 | ```js 26 | var React = require('react'); 27 | 28 | var Hello =
Hello {this.props.name}
; 29 | ``` 30 | 31 | ```js 32 | /** @jsx Foo.bar */ 33 | var Foo = require('foo'); 34 | 35 | var Hello =
Hello {this.props.name}
; 36 | ``` 37 | 38 | ## When Not To Use It 39 | 40 | If you are setting `React` as a global variable you can disable this rule. 41 | -------------------------------------------------------------------------------- /docs/rules/no-set-state.md: -------------------------------------------------------------------------------- 1 | # Prevent usage of setState (no-set-state) 2 | 3 | When using an architecture that separates your application state from your UI components (e.g. Flux), it may be desirable to forbid the use of local component state. This rule is especially helpful in read-only applications (that don't use forms), since local component state should rarely be necessary in such cases. 4 | 5 | ## Rule Details 6 | 7 | The following patterns are considered warnings: 8 | 9 | ```js 10 | var Hello = React.createClass({ 11 | getInitialState: function() { 12 | return { 13 | name: this.props.name 14 | }; 15 | }, 16 | handleClick: function() { 17 | this.setState({ 18 | name: this.props.name.toUpperCase() 19 | }); 20 | }, 21 | render: function() { 22 | return
Hello {this.state.name}
; 23 | } 24 | }); 25 | ``` 26 | 27 | The following patterns are not considered warnings: 28 | 29 | ```js 30 | var Hello = React.createClass({ 31 | render: function() { 32 | return
Hello {this.props.name}
; 33 | } 34 | }); 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/rules/no-string-refs.md: -------------------------------------------------------------------------------- 1 | # Prevent using string references (no-string-refs) 2 | 3 | Currently, two ways are supported by React to refer to components. The first one, providing a string identifier is considered legacy in the official documentation. Referring to components by setting a property on the `this` object in the reference callback is preferred. 4 | 5 | ## Rule Details 6 | 7 | Invalid: 8 | 9 | ```js 10 | var Hello = React.createClass({ 11 | render: function() { 12 | return
Hello, world.
; 13 | } 14 | }); 15 | ``` 16 | 17 | ```js 18 | var Hello = React.createClass({ 19 | componentDidMount: function() { 20 | var component = this.refs.hello; 21 | // ...do something with component 22 | }, 23 | render: function() { 24 | return
Hello, world.
; 25 | } 26 | }); 27 | ``` 28 | 29 | Valid: 30 | 31 | ```js 32 | var Hello = React.createClass({ 33 | componentDidMount: function() { 34 | var component = this.hello; 35 | // ...do something with component 36 | }, 37 | render() { 38 | return
this.hello = c}>Hello, world.
; 39 | } 40 | }); 41 | ``` 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Yannick Croissant 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /docs/rules/jsx-handler-names.md: -------------------------------------------------------------------------------- 1 | # Enforce event handler naming conventions in JSX (jsx-handler-names) 2 | 3 | Ensures that any component or prop methods used to handle events are correctly prefixed. 4 | 5 | ## Rule Details 6 | 7 | The following patterns are considered warnings: 8 | 9 | ```js 10 | 11 | ``` 12 | 13 | ```js 14 | 15 | ``` 16 | 17 | The following patterns are not considered warnings: 18 | 19 | ```js 20 | 21 | ``` 22 | 23 | ```js 24 | 25 | ``` 26 | 27 | ## Rule Options 28 | 29 | ```js 30 | ... 31 | "jsx-handler-names": [, { 32 | "eventHandlerPrefix": , 33 | "eventHandlerPropPrefix": 34 | }] 35 | ... 36 | ``` 37 | 38 | * `eventHandlerPrefix`: Prefix for component methods used as event handlers. Defaults to `handle` 39 | * `eventHandlerPropPrefix`: Prefix for props that are used as event handlers. Defaults to `on` 40 | 41 | ## When Not To Use It 42 | 43 | If you are not using JSX, or if you don't want to enforce specific naming conventions for event handlers. -------------------------------------------------------------------------------- /lib/rules/jsx-uses-react.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent React to be marked as unused 3 | * @author Glen Mailer 4 | */ 5 | 'use strict'; 6 | 7 | var variableUtil = require('../util/variable'); 8 | var pragmaUtil = require('../util/pragma'); 9 | 10 | // ------------------------------------------------------------------------------ 11 | // Rule Definition 12 | // ------------------------------------------------------------------------------ 13 | 14 | module.exports = function(context) { 15 | 16 | var pragma = pragmaUtil.getFromContext(context); 17 | 18 | // -------------------------------------------------------------------------- 19 | // Public 20 | // -------------------------------------------------------------------------- 21 | 22 | return { 23 | 24 | JSXOpeningElement: function() { 25 | variableUtil.markVariableAsUsed(context, pragma); 26 | }, 27 | 28 | BlockComment: function(node) { 29 | pragma = pragmaUtil.getFromNode(node) || pragma; 30 | } 31 | 32 | }; 33 | 34 | }; 35 | 36 | module.exports.schema = [{ 37 | type: 'object', 38 | properties: { 39 | pragma: { 40 | type: 'string' 41 | } 42 | }, 43 | additionalProperties: false 44 | }]; 45 | -------------------------------------------------------------------------------- /lib/rules/react-in-jsx-scope.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent missing React when using JSX 3 | * @author Glen Mailer 4 | */ 5 | 'use strict'; 6 | 7 | var variableUtil = require('../util/variable'); 8 | var pragmaUtil = require('../util/pragma'); 9 | 10 | // ----------------------------------------------------------------------------- 11 | // Rule Definition 12 | // ----------------------------------------------------------------------------- 13 | 14 | module.exports = function(context) { 15 | 16 | var pragma = pragmaUtil.getFromContext(context); 17 | var NOT_DEFINED_MESSAGE = '\'{{name}}\' must be in scope when using JSX'; 18 | 19 | return { 20 | 21 | JSXOpeningElement: function(node) { 22 | var variables = variableUtil.variablesInScope(context); 23 | if (variableUtil.findVariable(variables, pragma)) { 24 | return; 25 | } 26 | context.report({ 27 | node: node, 28 | message: NOT_DEFINED_MESSAGE, 29 | data: { 30 | name: pragma 31 | } 32 | }); 33 | }, 34 | 35 | BlockComment: function(node) { 36 | pragma = pragmaUtil.getFromNode(node) || pragma; 37 | } 38 | 39 | }; 40 | 41 | }; 42 | 43 | module.exports.schema = []; 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-react", 3 | "version": "5.1.1", 4 | "author": "Yannick Croissant ", 5 | "description": "React specific linting rules for ESLint", 6 | "main": "index.js", 7 | "scripts": { 8 | "coveralls": "cat ./reports/coverage/lcov.info | coveralls", 9 | "lint": "eslint ./", 10 | "test": "npm run lint && npm run unit-test", 11 | "unit-test": "istanbul cover --dir reports/coverage node_modules/mocha/bin/_mocha tests/**/*.js -- --reporter dot" 12 | }, 13 | "files": [ 14 | "LICENSE", 15 | "README.md", 16 | "index.js", 17 | "lib" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/yannickcr/eslint-plugin-react" 22 | }, 23 | "homepage": "https://github.com/yannickcr/eslint-plugin-react", 24 | "bugs": "https://github.com/yannickcr/eslint-plugin-react/issues", 25 | "devDependencies": { 26 | "babel-eslint": "6.0.4", 27 | "coveralls": "2.11.9", 28 | "eslint": "2.9.0", 29 | "istanbul": "0.4.3", 30 | "mocha": "2.4.5" 31 | }, 32 | "keywords": [ 33 | "eslint", 34 | "eslint-plugin", 35 | "eslintplugin", 36 | "react" 37 | ], 38 | "license": "MIT" 39 | } 40 | -------------------------------------------------------------------------------- /lib/rules/prefer-es6-class.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Enforce ES5 or ES6 class for React Components 3 | * @author Dan Hamilton 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 | var configuration = context.options[0] || 'always'; 15 | 16 | return { 17 | ObjectExpression: function(node) { 18 | if (utils.isES5Component(node) && configuration === 'always') { 19 | context.report({ 20 | node: node, 21 | message: 'Component should use es6 class instead of createClass' 22 | }); 23 | } 24 | }, 25 | ClassDeclaration: function(node) { 26 | if (utils.isES6Component(node) && configuration === 'never') { 27 | context.report({ 28 | node: node, 29 | message: 'Component should use createClass instead of es6 class' 30 | }); 31 | } 32 | } 33 | }; 34 | }); 35 | 36 | module.exports.schema = [{ 37 | enum: ['always', 'never'] 38 | }]; 39 | -------------------------------------------------------------------------------- /lib/rules/jsx-no-target-blank.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Forbid target='_blank' attribute 3 | * @author Kevin Miller 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Rule Definition 9 | // ------------------------------------------------------------------------------ 10 | 11 | module.exports = function(context) { 12 | return { 13 | JSXAttribute: function(node) { 14 | if (node.name.name === 'target' && node.value.value === '_blank') { 15 | var relFound = false; 16 | var attrs = node.parent.attributes; 17 | for (var idx in attrs) { 18 | if (attrs[idx].name.name === 'rel') { 19 | var tags = attrs[idx].value.value.split(' '); 20 | if (tags.indexOf('noopener') >= 0 && tags.indexOf('noreferrer') >= 0) { 21 | relFound = true; 22 | break; 23 | } 24 | } 25 | } 26 | if (!relFound) { 27 | context.report(node, 'Using target="_blank" without rel="noopener noreferrer" ' + 28 | 'is a security risk: see https://mathiasbynens.github.io/rel-noopener'); 29 | } 30 | } 31 | } 32 | }; 33 | }; 34 | 35 | module.exports.schema = []; 36 | -------------------------------------------------------------------------------- /docs/rules/no-deprecated.md: -------------------------------------------------------------------------------- 1 | # Prevent usage of deprecated methods (no-deprecated) 2 | 3 | Several methods are deprecated between React versions. This rule will warn you if you try to use a deprecated method. 4 | 5 | ## Rule Details 6 | 7 | The following patterns are considered warnings: 8 | 9 | ```js 10 | React.render(, root); 11 | 12 | React.unmountComponentAtNode(root); 13 | 14 | React.findDOMNode(this.refs.foo); 15 | 16 | React.renderToString(); 17 | 18 | React.renderToStaticMarkup(); 19 | ``` 20 | 21 | The following patterns are not considered warnings: 22 | 23 | ```js 24 | ReactDOM.render(, root); 25 | 26 | // When [1, {"react": "0.13.0"}] 27 | ReactDOM.findDOMNode(this.refs.foo); 28 | ``` 29 | 30 | ## Rule Options 31 | 32 | **Deprecation notice**: This option is deprecated, please use the [shared settings](/README.md#configuration) to specify the React version. 33 | 34 | By default this rule will warn to every methods marked as deprecated. You can limit it to an older React version if you are not using the latest one: 35 | 36 | ```js 37 | "rules": { 38 | "react/no-deprecated": [1, {"react": "0.12.0"}] // Will warn for every deprecated methods in React 0.12.0 and below 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /tests/lib/rules/no-danger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for no-danger 3 | * @author Scott Andrews 4 | */ 5 | 6 | 'use strict'; 7 | 8 | // ----------------------------------------------------------------------------- 9 | // Requirements 10 | // ----------------------------------------------------------------------------- 11 | 12 | var rule = require('../../../lib/rules/no-danger'); 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-danger', rule, { 28 | valid: [ 29 | {code: ';', parserOptions: parserOptions}, 30 | {code: ';', parserOptions: parserOptions}, 31 | {code: '
;', parserOptions: parserOptions} 32 | ], 33 | invalid: [{ 34 | code: '
;', 35 | errors: [{message: 'Dangerous property \'dangerouslySetInnerHTML\' found'}], 36 | parserOptions: parserOptions 37 | }] 38 | }); 39 | -------------------------------------------------------------------------------- /lib/rules/jsx-no-literals.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @fileoverview Prevent using string literals in React component definition 3 | * @author Caleb Morris 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Rule Definition 9 | // ------------------------------------------------------------------------------ 10 | 11 | module.exports = function(context) { 12 | 13 | function reportLiteralNode(node) { 14 | context.report({ 15 | node: node, 16 | message: 'Missing JSX expression container around literal string' 17 | }); 18 | } 19 | 20 | // -------------------------------------------------------------------------- 21 | // Public 22 | // -------------------------------------------------------------------------- 23 | 24 | return { 25 | 26 | Literal: function(node) { 27 | if ( 28 | !/^[\s]+$/.test(node.value) && 29 | node.parent && 30 | node.parent.type !== 'JSXExpressionContainer' && 31 | node.parent.type !== 'JSXAttribute' && 32 | node.parent.type.indexOf('JSX') !== -1 33 | ) { 34 | reportLiteralNode(node); 35 | } 36 | } 37 | 38 | }; 39 | 40 | }; 41 | 42 | module.exports.schema = [{ 43 | type: 'object', 44 | properties: {}, 45 | additionalProperties: false 46 | }]; 47 | -------------------------------------------------------------------------------- /lib/rules/no-set-state.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent usage of setState 3 | * @author Mark Dalgleish 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Rule Definition 9 | // ------------------------------------------------------------------------------ 10 | 11 | module.exports = function(context) { 12 | 13 | // -------------------------------------------------------------------------- 14 | // Public 15 | // -------------------------------------------------------------------------- 16 | 17 | return { 18 | 19 | CallExpression: function(node) { 20 | var callee = node.callee; 21 | if (callee.type !== 'MemberExpression') { 22 | return; 23 | } 24 | if (callee.object.type !== 'ThisExpression' || callee.property.name !== 'setState') { 25 | return; 26 | } 27 | var ancestors = context.getAncestors(callee); 28 | for (var i = 0, j = ancestors.length; i < j; i++) { 29 | if (ancestors[i].type === 'Property' || ancestors[i].type === 'MethodDefinition') { 30 | context.report({ 31 | node: callee, 32 | message: 'Do not use setState' 33 | }); 34 | break; 35 | } 36 | } 37 | } 38 | }; 39 | 40 | }; 41 | 42 | module.exports.schema = []; 43 | -------------------------------------------------------------------------------- /lib/rules/no-is-mounted.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent usage of isMounted 3 | * @author Joe Lencioni 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Rule Definition 9 | // ------------------------------------------------------------------------------ 10 | 11 | module.exports = function(context) { 12 | 13 | // -------------------------------------------------------------------------- 14 | // Public 15 | // -------------------------------------------------------------------------- 16 | 17 | return { 18 | 19 | CallExpression: function(node) { 20 | var callee = node.callee; 21 | if (callee.type !== 'MemberExpression') { 22 | return; 23 | } 24 | if (callee.object.type !== 'ThisExpression' || callee.property.name !== 'isMounted') { 25 | return; 26 | } 27 | var ancestors = context.getAncestors(callee); 28 | for (var i = 0, j = ancestors.length; i < j; i++) { 29 | if (ancestors[i].type === 'Property' || ancestors[i].type === 'MethodDefinition') { 30 | context.report({ 31 | node: callee, 32 | message: 'Do not use isMounted' 33 | }); 34 | break; 35 | } 36 | } 37 | } 38 | }; 39 | 40 | }; 41 | 42 | module.exports.schema = []; 43 | -------------------------------------------------------------------------------- /docs/rules/jsx-boolean-value.md: -------------------------------------------------------------------------------- 1 | # Enforce boolean attributes notation in JSX (jsx-boolean-value) 2 | 3 | In JSX when using a boolean attribute you can set the attribute value to `true` or omit the value. This rule will enforce one or the other to keep consistency in your code. 4 | 5 | **Fixable:** This rule is automatically fixable using the `--fix` flag on the command line. 6 | 7 | ## Rule Details 8 | 9 | This rule takes one argument. If it is `"always"` then it warns whenever an attribute is missing its value. If `"never"` then it warns if an attribute has a `true` value. The default value of this option is `"never"`. 10 | 11 | The following patterns are considered warnings when configured `"never"`: 12 | 13 | ```js 14 | var Hello = ; 15 | ``` 16 | 17 | The following patterns are not considered warnings when configured `"never"`: 18 | 19 | ```js 20 | var Hello = ; 21 | ``` 22 | 23 | The following patterns are considered warnings when configured `"always"`: 24 | 25 | ```js 26 | var Hello = ; 27 | ``` 28 | 29 | The following patterns are not considered warnings when configured `"always"`: 30 | 31 | ```js 32 | var Hello = ; 33 | ``` 34 | 35 | ## When Not To Use It 36 | 37 | If you do not want to enforce any style for boolean attributes, then you can disable this rule. 38 | -------------------------------------------------------------------------------- /lib/rules/jsx-sort-prop-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Enforce propTypes declarations alphabetical sorting 3 | * @deprecated 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Rule Definition 9 | // ------------------------------------------------------------------------------ 10 | 11 | var util = require('util'); 12 | var sortPropTypes = require('./sort-prop-types'); 13 | var isWarnedForDeprecation = false; 14 | 15 | module.exports = function(context) { 16 | return util._extend(sortPropTypes(context), { 17 | Program: function() { 18 | if (isWarnedForDeprecation || /\=-(f|-format)=/.test(process.argv.join('='))) { 19 | return; 20 | } 21 | 22 | /* eslint-disable no-console */ 23 | console.log('The react/jsx-sort-prop-types rule is deprecated. Please ' + 24 | 'use the react/sort-prop-types rule instead.'); 25 | /* eslint-enable no-console */ 26 | isWarnedForDeprecation = true; 27 | } 28 | }); 29 | }; 30 | 31 | module.exports.schema = [{ 32 | type: 'object', 33 | properties: { 34 | requiredFirst: { 35 | type: 'boolean' 36 | }, 37 | callbacksLast: { 38 | type: 'boolean' 39 | }, 40 | ignoreCase: { 41 | type: 'boolean' 42 | } 43 | }, 44 | additionalProperties: false 45 | }]; 46 | -------------------------------------------------------------------------------- /docs/rules/jsx-pascal-case.md: -------------------------------------------------------------------------------- 1 | # Enforce PascalCase for user-defined JSX components (jsx-pascal-case) 2 | 3 | Enforces coding style that user-defined JSX components are defined and referenced in PascalCase. 4 | 5 | Note that since React's JSX uses the upper vs. lower case convention to distinguish between local component classes and HTML tags this rule will not warn on components that start with a lower case letter. 6 | 7 | ## Rule Details 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```js 12 | 13 | ``` 14 | 15 | ```js 16 | 17 | ``` 18 | 19 | The following patterns are not considered warnings: 20 | 21 | ```js 22 |
23 | ``` 24 | 25 | ```js 26 | 27 | ``` 28 | 29 | ```js 30 | 31 |
32 | 33 | ``` 34 | 35 | ```js 36 | 37 | ``` 38 | 39 | ## Rule Options 40 | 41 | ```js 42 | ... 43 | "jsx-pascal-case": [, { allowAllCaps: , ignore: }] 44 | ... 45 | ``` 46 | 47 | * `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0. 48 | * `allowAllCaps`: optional boolean set to `true` to allow components name in all caps (default to `false`). 49 | * `ignore`: optional array of components name to ignore during validation. 50 | 51 | ## When Not To Use It 52 | 53 | If you are not using JSX. 54 | -------------------------------------------------------------------------------- /lib/rules/jsx-no-duplicate-props.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Enforce no duplicate props 3 | * @author Markus Ånöstam 4 | */ 5 | 6 | 'use strict'; 7 | 8 | // ------------------------------------------------------------------------------ 9 | // Rule Definition 10 | // ------------------------------------------------------------------------------ 11 | 12 | module.exports = function (context) { 13 | 14 | var configuration = context.options[0] || {}; 15 | var ignoreCase = configuration.ignoreCase || false; 16 | 17 | return { 18 | JSXOpeningElement: function (node) { 19 | var props = {}; 20 | 21 | node.attributes.forEach(function(decl) { 22 | if (decl.type === 'JSXSpreadAttribute') { 23 | return; 24 | } 25 | 26 | var name = decl.name.name; 27 | 28 | if (ignoreCase) { 29 | name = name.toLowerCase(); 30 | } 31 | 32 | if (props.hasOwnProperty(name)) { 33 | context.report({ 34 | node: decl, 35 | message: 'No duplicate props allowed' 36 | }); 37 | } else { 38 | props[name] = 1; 39 | } 40 | }); 41 | } 42 | }; 43 | }; 44 | 45 | module.exports.schema = [{ 46 | type: 'object', 47 | properties: { 48 | ignoreCase: { 49 | type: 'boolean' 50 | } 51 | }, 52 | additionalProperties: false 53 | }]; 54 | -------------------------------------------------------------------------------- /lib/util/version.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Utility functions for React version configuration 3 | * @author Yannick Croissant 4 | */ 5 | 'use strict'; 6 | 7 | function getFromContext(context) { 8 | var confVer = '999.999.999'; 9 | // .eslintrc shared settings (http://eslint.org/docs/user-guide/configuring#adding-shared-settings) 10 | if (context.settings.react && context.settings.react.version) { 11 | confVer = context.settings.react.version; 12 | // Deprecated react option, here for backward compatibility 13 | } else if (context.options[0] && context.options[0].react) { 14 | confVer = context.options[0].react; 15 | } 16 | confVer = /^[0-9]+\.[0-9]+$/.test(confVer) ? confVer + '.0' : confVer; 17 | return confVer.split('.').map(function(part) { 18 | return Number(part); 19 | }); 20 | } 21 | 22 | function test(context, methodVer) { 23 | var confVer = getFromContext(context); 24 | methodVer = methodVer.split('.').map(function(part) { 25 | return Number(part); 26 | }); 27 | var higherMajor = methodVer[0] < confVer[0]; 28 | var higherMinor = methodVer[0] === confVer[0] && methodVer[1] < confVer[1]; 29 | var higherOrEqualPatch = methodVer[0] === confVer[0] && methodVer[1] === confVer[1] && methodVer[2] <= confVer[2]; 30 | 31 | return higherMajor || higherMinor || higherOrEqualPatch; 32 | } 33 | 34 | module.exports = { 35 | test: test 36 | }; 37 | -------------------------------------------------------------------------------- /docs/rules/wrap-multilines.md: -------------------------------------------------------------------------------- 1 | # Prevent missing parentheses around multiline JSX (wrap-multilines) 2 | 3 | Wrapping multiline JSX in parentheses can improve readability and/or convenience. It optionally takes a second parameter in the form of an object, containing places to apply the rule. By default, `"declaration"`, `"assignment"`, and `"return"` syntax is checked, but these can be explicitly disabled. Any syntax type missing in the object will follow the default behavior (become enabled). 4 | 5 | **Fixable:** This rule is automatically fixable using the `--fix` flag on the command line. 6 | 7 | ## Rule Details 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```js 12 | var Hello = React.createClass({ 13 | render: function() { 14 | return
15 |

Hello {this.props.name}

16 |
; 17 | } 18 | }); 19 | ``` 20 | 21 | The following patterns are not considered warnings: 22 | 23 | ```js 24 | var singleLineJSX =

Hello

25 | 26 | var Hello = React.createClass({ 27 | render: function() { 28 | return ( 29 |
30 |

Hello {this.props.name}

31 |
32 | ); 33 | } 34 | }); 35 | 36 | // When [1, {declaration: false}] 37 | var hello; 38 | hello =
39 |

Hello

40 |
41 | 42 | // When [1, {declaration: true, assignment: false, return: true}] 43 | var world =
44 |

World

45 |
46 | ``` 47 | -------------------------------------------------------------------------------- /docs/rules/prefer-es6-class.md: -------------------------------------------------------------------------------- 1 | # Enforce ES5 or ES6 class for React Components (prefer-es6-class) 2 | 3 | React offers you two way to create traditional components: using the ES5 `React.createClass` method or the new ES6 class system. This rule allow you to enforce one way or another. 4 | 5 | ## Rule Options 6 | 7 | ```js 8 | ... 9 | "prefer-es6-class": [, ] 10 | ... 11 | ``` 12 | 13 | ### `always` mode 14 | 15 | Will enforce ES6 classes for React Components. This is the default mode. 16 | 17 | The following patterns are considered warnings: 18 | 19 | ```js 20 | var Hello = React.createClass({ 21 | render: function() { 22 | return
Hello {this.props.name}
; 23 | } 24 | }); 25 | ``` 26 | 27 | The following patterns are not considered warnings: 28 | 29 | ```js 30 | class Hello extends React.Component { 31 | render() { 32 | return
Hello {this.props.name}
; 33 | } 34 | } 35 | ``` 36 | 37 | ### `never` mode 38 | 39 | Will enforce ES5 classes for React Components 40 | 41 | The following patterns are considered warnings: 42 | 43 | ```js 44 | class Hello extends React.Component { 45 | render() { 46 | return
Hello {this.props.name}
; 47 | } 48 | } 49 | ``` 50 | 51 | The following patterns are not considered warnings: 52 | 53 | ```js 54 | var Hello = React.createClass({ 55 | render: function() { 56 | return
Hello {this.props.name}
; 57 | } 58 | }); 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/rules/jsx-indent.md: -------------------------------------------------------------------------------- 1 | # Validate JSX indentation (jsx-indent) 2 | 3 | This option validates a specific indentation style for JSX. 4 | 5 | ## Rule Details 6 | 7 | This rule is aimed to enforce consistent indentation style. The default style is `4 spaces`. 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```jsx 12 | // 2 spaces indentation 13 | 14 | 15 | 16 | 17 | // no indentation 18 | 19 | 20 | 21 | 22 | // 1 tab indentation 23 | 24 | 25 | 26 | ``` 27 | 28 | ## Rule Options 29 | 30 | It takes an option as the second parameter which can be `"tab"` for tab-based indentation or a positive number for space indentations. 31 | 32 | ```js 33 | ... 34 | "jsx-indent": [, 'tab'|] 35 | ... 36 | ``` 37 | 38 | The following patterns are considered warnings: 39 | 40 | ```jsx 41 | // 2 spaces indentation 42 | // [2, 2] 43 | 44 | 45 | 46 | 47 | // tab indentation 48 | // [2, 'tab'] 49 | 50 | 51 | 52 | ``` 53 | 54 | The following patterns are not warnings: 55 | 56 | ```jsx 57 | 58 | // 2 spaces indentation 59 | // [2, 2] 60 | 61 | 62 | 63 | 64 | // tab indentation 65 | // [2, 'tab'] 66 | 67 | 68 | 69 | 70 | // no indentation 71 | // [2, 0] 72 | 73 | 74 | 75 | ``` 76 | 77 | ## When not to use 78 | 79 | If you are not using JSX then you can disable this rule. 80 | -------------------------------------------------------------------------------- /docs/rules/jsx-space-before-closing.md: -------------------------------------------------------------------------------- 1 | # Validate spacing before closing bracket in JSX (jsx-space-before-closing) 2 | 3 | Enforce or forbid spaces before the closing bracket of self-closing JSX elements. 4 | 5 | ## Rule Details 6 | 7 | This rule checks if there is one or more spaces before the closing bracket of self-closing JSX elements. 8 | 9 | This rule takes one argument. If it is `"always"` then it warns whenever a space is missing before the closing bracket. If `"never"` then it warns if a space is present before the closing bracket. The default value of this option is `"always"`. 10 | 11 | The following patterns are considered warnings when configured `"always"`: 12 | 13 | ```js 14 | 15 | 16 | ``` 17 | 18 | The following patterns are not considered warnings when configured `"always"`: 19 | 20 | ```js 21 | 22 | 23 | 27 | ``` 28 | 29 | The following patterns are considered warnings when configured `"never"`: 30 | 31 | ```js 32 | 33 | 34 | ``` 35 | 36 | The following patterns are not considered warnings when configured `"never"`: 37 | 38 | ```js 39 | 40 | 41 | 45 | ``` 46 | 47 | ## When Not To Use It 48 | 49 | You can turn this rule off if you are not concerned with the consistency of spacing before closing brackets. 50 | -------------------------------------------------------------------------------- /docs/rules/jsx-max-props-per-line.md: -------------------------------------------------------------------------------- 1 | # Limit maximum of props on a single line in JSX (jsx-max-props-per-line) 2 | 3 | Limiting the maximum of props on a single line can improve readability. 4 | 5 | ## Rule Details 6 | 7 | This rule checks all JSX elements and verifies that the number of props per line do not exceed the maximum allowed. A spread attribute counts as one prop. This rule is off by default and when on the default maximum of props on one line is `1`. 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```jsx 12 | ; 13 | ``` 14 | 15 | The following patterns are not considered warnings: 16 | 17 | ```jsx 18 | ; 22 | 23 | ; 28 | ``` 29 | 30 | ## Rule Options 31 | 32 | ```js 33 | ... 34 | "jsx-max-props-per-line": [, { "maximum": }] 35 | ... 36 | ``` 37 | 38 | ### `maximum` 39 | 40 | Maximum number of props allowed on a single line. Default to `1`. 41 | 42 | The following patterns are considered warnings: 43 | 44 | ```jsx 45 | // [1, {maximum: 2}] 46 | ; 47 | ``` 48 | 49 | The following patterns are not considered warnings: 50 | 51 | ```jsx 52 | // [1, {maximum: 2}] 53 | ; 57 | ``` 58 | 59 | ## When not to use 60 | 61 | If you are not using JSX then you can disable this rule. 62 | -------------------------------------------------------------------------------- /lib/rules/self-closing-comp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent extra closing tags for components without children 3 | * @author Yannick Croissant 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Rule Definition 9 | // ------------------------------------------------------------------------------ 10 | 11 | module.exports = function(context) { 12 | 13 | var tagConvention = /^[a-z]|\-/; 14 | function isTagName(name) { 15 | return tagConvention.test(name); 16 | } 17 | 18 | function isComponent(node) { 19 | return node.name && node.name.type === 'JSXIdentifier' && !isTagName(node.name.name); 20 | } 21 | 22 | function hasChildren(node) { 23 | var childrens = node.parent.children; 24 | if ( 25 | !childrens.length || 26 | (childrens.length === 1 && childrens[0].type === 'Literal' && !childrens[0].value.replace(/(?!\xA0)\s/g, '')) 27 | ) { 28 | return false; 29 | } 30 | return true; 31 | } 32 | 33 | // -------------------------------------------------------------------------- 34 | // Public 35 | // -------------------------------------------------------------------------- 36 | 37 | return { 38 | 39 | JSXOpeningElement: function(node) { 40 | if (!isComponent(node) || node.selfClosing || hasChildren(node)) { 41 | return; 42 | } 43 | context.report({ 44 | node: node, 45 | message: 'Empty components are self-closing' 46 | }); 47 | } 48 | }; 49 | 50 | }; 51 | 52 | module.exports.schema = []; 53 | -------------------------------------------------------------------------------- /docs/rules/require-extension.md: -------------------------------------------------------------------------------- 1 | # Restrict file extensions that may be required (require-extension) 2 | 3 | `require()` statements should generally not include a file extension as there is a well defined mechanism for resolving a module ID to a specific file. This rule inspects the module ID being required and creates a warning if the ID contains a '.jsx' file extension. 4 | 5 | Note: this rule does not prevent required files from containing these extensions, it merely prevents the extension from being included in the `require()` statement. 6 | 7 | ## Rule Details 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```js 12 | var index = require('./index.jsx'); 13 | 14 | // When [1, {extensions: ['.js']}] 15 | var index = require('./index.js'); 16 | ``` 17 | 18 | The following patterns are not considered warnings: 19 | 20 | ```js 21 | var index = require('./index'); 22 | 23 | var eslint = require('eslint'); 24 | ``` 25 | 26 | ## Rule Options 27 | 28 | The set of forbidden extensions is configurable. By default '.jsx' is blocked. If you wanted to forbid both '.jsx' and '.js', the configuration would be: 29 | 30 | ```js 31 | "rules": { 32 | "react/require-extension": [1, { "extensions": [".js", ".jsx"] }], 33 | } 34 | ``` 35 | 36 | To configure WebPack to resolve '.jsx' add the following to `webpack.config.js`: 37 | 38 | ```js 39 | resolve: { 40 | extensions: ["", ".js", ".jsx"] 41 | }, 42 | ``` 43 | 44 | ## When Not To Use It 45 | 46 | If you have file in your project with a '.jsx' file extension and do not have `require()` configured to automatically resolve '.jsx' files. 47 | -------------------------------------------------------------------------------- /lib/rules/jsx-first-prop-new-line.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure proper position of the first property in JSX 3 | * @author Joachim Seminck 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Rule Definition 9 | // ------------------------------------------------------------------------------ 10 | 11 | module.exports = function (context) { 12 | var configuration = context.options[0]; 13 | 14 | function isMultilineJSX(jsxNode) { 15 | return jsxNode.loc.start.line < jsxNode.loc.end.line; 16 | } 17 | 18 | return { 19 | JSXOpeningElement: function (node) { 20 | if ((configuration === 'multiline' && isMultilineJSX(node)) || (configuration === 'always')) { 21 | node.attributes.forEach(function(decl) { 22 | if (decl.loc.start.line === node.loc.start.line) { 23 | context.report({ 24 | node: decl, 25 | message: 'Property should be placed on a new line' 26 | }); 27 | } 28 | }); 29 | } else if (configuration === 'never' && node.attributes.length > 0) { 30 | var firstNode = node.attributes[0]; 31 | if (node.loc.start.line < firstNode.loc.start.line) { 32 | context.report({ 33 | node: firstNode, 34 | message: 'Property should be placed on the same line as the component declaration' 35 | }); 36 | return; 37 | } 38 | } 39 | return; 40 | } 41 | }; 42 | }; 43 | 44 | module.exports.schema = [{ 45 | enum: ['always', 'never', 'multiline'] 46 | }]; 47 | -------------------------------------------------------------------------------- /docs/rules/jsx-indent-props.md: -------------------------------------------------------------------------------- 1 | # Validate props indentation in JSX (jsx-indent-props) 2 | 3 | This option validates a specific indentation style for props. 4 | 5 | **Fixable:** This rule is automatically fixable using the `--fix` flag on the command line. 6 | 7 | ## Rule Details 8 | 9 | This rule is aimed to enforce consistent indentation style. The default style is `4 spaces`. 10 | 11 | The following patterns are considered warnings: 12 | 13 | ```jsx 14 | // 2 spaces indentation 15 | 18 | 19 | // no indentation 20 | 23 | 24 | // 1 tab indentation 25 | 28 | ``` 29 | 30 | ## Rule Options 31 | 32 | It takes an option as the second parameter which can be `"tab"` for tab-based indentation or a positive number for space indentations. 33 | 34 | ```js 35 | ... 36 | "jsx-indent-props": [, 'tab'|] 37 | ... 38 | ``` 39 | 40 | The following patterns are considered warnings: 41 | 42 | ```jsx 43 | // 2 spaces indentation 44 | // [2, 2] 45 | 48 | 49 | // tab indentation 50 | // [2, 'tab'] 51 | 54 | ``` 55 | 56 | The following patterns are not warnings: 57 | 58 | ```jsx 59 | 60 | // 2 spaces indentation 61 | // [2, 2] 62 | 65 | 66 | 68 | 69 | // tab indentation 70 | // [2, 'tab'] 71 | 74 | 75 | // no indentation 76 | // [2, 0] 77 | 80 | ``` 81 | 82 | ## When not to use 83 | 84 | If you are not using JSX then you can disable this rule. 85 | -------------------------------------------------------------------------------- /lib/rules/jsx-boolean-value.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Enforce boolean attributes notation in JSX 3 | * @author Yannick Croissant 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Rule Definition 9 | // ------------------------------------------------------------------------------ 10 | 11 | module.exports = function(context) { 12 | 13 | var configuration = context.options[0] || 'never'; 14 | 15 | var NEVER_MESSAGE = 'Value must be omitted for boolean attributes'; 16 | var ALWAYS_MESSAGE = 'Value must be set for boolean attributes'; 17 | 18 | return { 19 | JSXAttribute: function(node) { 20 | switch (configuration) { 21 | case 'always': 22 | if (node.value === null) { 23 | context.report({ 24 | node: node, 25 | message: ALWAYS_MESSAGE, 26 | fix: function(fixer) { 27 | return fixer.insertTextAfter(node, '={true}'); 28 | } 29 | }); 30 | } 31 | break; 32 | case 'never': 33 | if (node.value && node.value.type === 'JSXExpressionContainer' && node.value.expression.value === true) { 34 | context.report({ 35 | node: node, 36 | message: NEVER_MESSAGE, 37 | fix: function(fixer) { 38 | return fixer.removeRange([node.name.range[1], node.value.range[1]]); 39 | } 40 | }); 41 | } 42 | break; 43 | default: 44 | break; 45 | } 46 | } 47 | }; 48 | }; 49 | 50 | module.exports.schema = [{ 51 | enum: ['always', 'never'] 52 | }]; 53 | -------------------------------------------------------------------------------- /docs/rules/prefer-stateless-function.md: -------------------------------------------------------------------------------- 1 | # Enforce stateless React Components to be written as a pure function (prefer-stateless-function) 2 | 3 | Stateless functional components are more simple than class based components and will benefit from future React performance optimizations specific to these components. 4 | 5 | ## Rule Details 6 | 7 | This rule will check your class based React components for 8 | 9 | * methods/properties other than `displayName`, `propTypes`, `render` and useless constructor (same detection as ESLint [no-useless-constructor rule](http://eslint.org/docs/rules/no-useless-constructor)) 10 | * instance property other than `this.props` and `this.context` 11 | * presence of `ref` attribute in JSX 12 | * `render` method that return anything but JSX: `undefined`, `null`, etc. (only in React <15.0.0, see [shared settings](https://github.com/yannickcr/eslint-plugin-react/blob/master/README.md#configuration) for React version configuration) 13 | 14 | If none of these 4 elements are found, the rule will warn you to write this component as a pure function. 15 | 16 | The following pattern is considered warnings: 17 | 18 | ```js 19 | var Hello = React.createClass({ 20 | render: function() { 21 | return
Hello {this.props.name}
; 22 | } 23 | }); 24 | ``` 25 | 26 | The following pattern is not considered warnings: 27 | 28 | ```js 29 | const Foo = function(props) { 30 | return
{props.foo}
; 31 | }; 32 | ``` 33 | 34 | The following pattern is not considered warning in React <15.0.0: 35 | 36 | ```js 37 | class Foo extends React.Component { 38 | render() { 39 | if (!this.props.foo) { 40 | return null 41 | } 42 | return
{this.props.foo}
; 43 | } 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/rules/forbid-prop-types.md: -------------------------------------------------------------------------------- 1 | # Forbid certain propTypes (forbid-prop-types) 2 | 3 | By default this rule prevents vague prop types with more specific alternatives available (`any`, `array`, `object`), but any prop type can be disabled if desired. The defaults are chosen because they have obvious replacements. `any` should be replaced with, well, anything. `array` and `object` can be replaced with `arrayOf` and `shape`, respectively. 4 | 5 | ## Rule Details 6 | 7 | This rule checks all JSX components and verifies that no forbidden propsTypes are used. 8 | This rule is off by default. 9 | 10 | The following patterns are considered warnings: 11 | 12 | ```js 13 | var Component = React.createClass({ 14 | propTypes: { 15 | a: React.PropTypes.any, 16 | r: React.PropTypes.array, 17 | o: React.PropTypes.object 18 | }, 19 | ... 20 | }); 21 | 22 | class Component extends React.Component { 23 | ... 24 | } 25 | Component.propTypes = { 26 | a: React.PropTypes.any, 27 | r: React.PropTypes.array, 28 | o: React.PropTypes.object 29 | }; 30 | 31 | class Component extends React.Component { 32 | static propTypes = { 33 | a: React.PropTypes.any, 34 | r: React.PropTypes.array, 35 | o: React.PropTypes.object 36 | } 37 | render() { 38 | return
; 39 | } 40 | } 41 | ``` 42 | 43 | ## Rule Options 44 | 45 | ```js 46 | ... 47 | "forbid-prop-types": [, { "forbid": [] }] 48 | ... 49 | ``` 50 | 51 | ### `forbid` 52 | 53 | An array of strings, with the names of React.PropType keys that are forbidden. 54 | 55 | ## When not to use 56 | 57 | This rule is a formatting/documenting preference and not following it won't negatively affect the quality of your code. This rule encourages prop types that more specifically document their usage. 58 | -------------------------------------------------------------------------------- /lib/rules/jsx-max-props-per-line.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Limit maximum of props on a single line in JSX 3 | * @author Yannick Croissant 4 | */ 5 | 6 | 'use strict'; 7 | 8 | // ------------------------------------------------------------------------------ 9 | // Rule Definition 10 | // ------------------------------------------------------------------------------ 11 | 12 | module.exports = function (context) { 13 | 14 | var sourceCode = context.getSourceCode(); 15 | var configuration = context.options[0] || {}; 16 | var maximum = configuration.maximum || 1; 17 | 18 | function getPropName(propNode) { 19 | if (propNode.type === 'JSXSpreadAttribute') { 20 | return sourceCode.getText(propNode.argument); 21 | } 22 | return propNode.name.name; 23 | } 24 | 25 | return { 26 | JSXOpeningElement: function (node) { 27 | var props = {}; 28 | 29 | node.attributes.forEach(function(decl) { 30 | var line = decl.loc.start.line; 31 | if (props[line]) { 32 | props[line].push(decl); 33 | } else { 34 | props[line] = [decl]; 35 | } 36 | }); 37 | 38 | for (var line in props) { 39 | if (!props.hasOwnProperty(line)) { 40 | continue; 41 | } 42 | if (props[line].length > maximum) { 43 | var name = getPropName(props[line][maximum]); 44 | context.report({ 45 | node: props[line][maximum], 46 | message: 'Prop `' + name + '` must be placed on a new line' 47 | }); 48 | break; 49 | } 50 | } 51 | } 52 | }; 53 | }; 54 | 55 | module.exports.schema = [{ 56 | type: 'object', 57 | properties: { 58 | maximum: { 59 | type: 'integer', 60 | minimum: 1 61 | } 62 | } 63 | }]; 64 | -------------------------------------------------------------------------------- /docs/rules/jsx-equals-spacing.md: -------------------------------------------------------------------------------- 1 | # Enforce or disallow spaces around equal signs in JSX attributes. (jsx-equals-spacing) 2 | 3 | Some style guides require or disallow spaces around equal signs. 4 | 5 | **Fixable:** This rule is automatically fixable using the `--fix` flag on the command line. 6 | 7 | ## Rule Details 8 | 9 | This rule will enforce consistency of spacing around equal signs in JSX attributes, by requiring or disallowing one or more spaces before and after `=`. 10 | 11 | ### Options 12 | 13 | There are two options for the rule: 14 | 15 | * `"always"` enforces spaces around the equal sign 16 | * `"never"` disallows spaces around the equal sign (default) 17 | 18 | Depending on your coding conventions, you can choose either option by specifying it in your configuration: 19 | 20 | ```json 21 | "jsx-equals-spacing": [2, "always"] 22 | ``` 23 | 24 | #### never 25 | 26 | When `"never"` is set, the following patterns are considered warnings: 27 | 28 | ```js 29 | ; 30 | ; 31 | ; 32 | ``` 33 | 34 | The following patterns are not warnings: 35 | 36 | ```js 37 | ; 38 | ; 39 | ; 40 | ``` 41 | 42 | #### always 43 | 44 | When `"always"` is used, the following patterns are considered warnings: 45 | 46 | ```js 47 | ; 48 | ; 49 | ; 50 | ``` 51 | 52 | The following patterns are not warnings: 53 | 54 | ```js 55 | ; 56 | ; 57 | ; 58 | ``` 59 | 60 | ## When Not To Use It 61 | 62 | You can turn this rule off if you are not concerned with the consistency of spacing around equal signs in JSX attributes. 63 | 64 | -------------------------------------------------------------------------------- /docs/rules/jsx-uses-react.md: -------------------------------------------------------------------------------- 1 | # Prevent React to be incorrectly marked as unused (jsx-uses-react) 2 | 3 | JSX expands to a call to `React.createElement`, a file which includes `React` 4 | but only uses JSX should consider the `React` variable as used. 5 | 6 | If you are using the @jsx pragma this rule will mark the designated variable and not the `React` one. 7 | 8 | This rule has no effect if the `no-unused-vars` rule is not enabled. 9 | 10 | 11 | ## Rule Details 12 | 13 | The following patterns are considered warnings: 14 | 15 | ```js 16 | var React = require('react'); 17 | 18 | // nothing to do with React 19 | ``` 20 | 21 | ```js 22 | /** @jsx Foo */ 23 | var React = require('react'); 24 | 25 | var Hello =
Hello {this.props.name}
; 26 | ``` 27 | 28 | The following patterns are not considered warnings: 29 | 30 | ```js 31 | var React = require('react'); 32 | 33 | var Hello =
Hello {this.props.name}
; 34 | ``` 35 | 36 | ```js 37 | /** @jsx Foo */ 38 | var Foo = require('foo'); 39 | 40 | var Hello =
Hello {this.props.name}
; 41 | ``` 42 | 43 | ## Rule Options 44 | 45 | ```js 46 | ... 47 | "jsx-uses-react": [, { "pragma": }] 48 | ... 49 | ``` 50 | 51 | ### `pragma` 52 | 53 | **Deprecation notice**: This option is deprecated, please use the [shared settings](/README.md#configuration) to specify a custom pragma. 54 | 55 | As an alternative to specifying the above pragma in each source file, you can specify 56 | this configuration option: 57 | 58 | ```js 59 | var Foo = require('Foo'); 60 | 61 | var Hello =
Hello {this.props.name}
; 62 | ``` 63 | 64 | 65 | ## When Not To Use It 66 | 67 | If you are not using JSX, if React is declared as global variable or if you do not use the `no-unused-vars` rule then you can disable this rule. 68 | -------------------------------------------------------------------------------- /lib/rules/no-did-mount-set-state.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent usage of setState in componentDidMount 3 | * @author Yannick Croissant 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Rule Definition 9 | // ------------------------------------------------------------------------------ 10 | 11 | module.exports = function(context) { 12 | 13 | var mode = context.options[0] || 'never'; 14 | 15 | // -------------------------------------------------------------------------- 16 | // Public 17 | // -------------------------------------------------------------------------- 18 | 19 | return { 20 | 21 | CallExpression: function(node) { 22 | var callee = node.callee; 23 | if ( 24 | callee.type !== 'MemberExpression' || 25 | callee.object.type !== 'ThisExpression' || 26 | callee.property.name !== 'setState' 27 | ) { 28 | return; 29 | } 30 | var ancestors = context.getAncestors(callee).reverse(); 31 | var depth = 0; 32 | for (var i = 0, j = ancestors.length; i < j; i++) { 33 | if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) { 34 | depth++; 35 | } 36 | if ( 37 | (ancestors[i].type !== 'Property' && ancestors[i].type !== 'MethodDefinition') || 38 | ancestors[i].key.name !== 'componentDidMount' || 39 | (mode === 'allow-in-func' && depth > 1) 40 | ) { 41 | continue; 42 | } 43 | context.report({ 44 | node: callee, 45 | message: 'Do not use setState in componentDidMount' 46 | }); 47 | break; 48 | } 49 | } 50 | }; 51 | 52 | }; 53 | 54 | module.exports.schema = [{ 55 | enum: ['allow-in-func'] 56 | }]; 57 | -------------------------------------------------------------------------------- /lib/rules/no-did-update-set-state.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent usage of setState in componentDidUpdate 3 | * @author Yannick Croissant 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Rule Definition 9 | // ------------------------------------------------------------------------------ 10 | 11 | module.exports = function(context) { 12 | 13 | var mode = context.options[0] || 'never'; 14 | 15 | // -------------------------------------------------------------------------- 16 | // Public 17 | // -------------------------------------------------------------------------- 18 | 19 | return { 20 | 21 | CallExpression: function(node) { 22 | var callee = node.callee; 23 | if ( 24 | callee.type !== 'MemberExpression' || 25 | callee.object.type !== 'ThisExpression' || 26 | callee.property.name !== 'setState' 27 | ) { 28 | return; 29 | } 30 | var ancestors = context.getAncestors(callee).reverse(); 31 | var depth = 0; 32 | for (var i = 0, j = ancestors.length; i < j; i++) { 33 | if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) { 34 | depth++; 35 | } 36 | if ( 37 | (ancestors[i].type !== 'Property' && ancestors[i].type !== 'MethodDefinition') || 38 | ancestors[i].key.name !== 'componentDidUpdate' || 39 | (mode === 'allow-in-func' && depth > 1) 40 | ) { 41 | continue; 42 | } 43 | context.report({ 44 | node: callee, 45 | message: 'Do not use setState in componentDidUpdate' 46 | }); 47 | break; 48 | } 49 | } 50 | }; 51 | 52 | }; 53 | 54 | module.exports.schema = [{ 55 | enum: ['allow-in-func'] 56 | }]; 57 | -------------------------------------------------------------------------------- /docs/rules/no-multi-comp.md: -------------------------------------------------------------------------------- 1 | # Prevent multiple component definition per file (no-multi-comp) 2 | 3 | Declaring only one component per file improves readability and reusability of components. 4 | 5 | ## Rule Details 6 | 7 | The following patterns are considered warnings: 8 | 9 | ```js 10 | var Hello = React.createClass({ 11 | render: function() { 12 | return
Hello {this.props.name}
; 13 | } 14 | }); 15 | 16 | var HelloJohn = React.createClass({ 17 | render: function() { 18 | return ; 19 | } 20 | }); 21 | ``` 22 | 23 | The following patterns are not considered warnings: 24 | 25 | ```js 26 | var Hello = require('./components/Hello'); 27 | 28 | var HelloJohn = React.createClass({ 29 | render: function() { 30 | return ; 31 | } 32 | }); 33 | ``` 34 | 35 | ## Rule Options 36 | 37 | ```js 38 | ... 39 | "no-multi-comp": [, { "ignoreStateless": }] 40 | ... 41 | ``` 42 | 43 | ### `ignoreStateless` 44 | 45 | When `true` the rule will ignore stateless components and will allow you to have multiple stateless components, or one statefull component and some stateless components in the same file. 46 | 47 | The following patterns are considered okay and do not cause warnings: 48 | 49 | ```js 50 | function Hello(props) { 51 | return
Hello {props.name}
; 52 | } 53 | function HelloAgain(props) { 54 | return
Hello again {props.name}
; 55 | } 56 | ``` 57 | 58 | ```js 59 | function Hello(props) { 60 | return
Hello {props.name}
; 61 | } 62 | class HelloJohn extends React.Component { 63 | render() { 64 | return ; 65 | } 66 | } 67 | module.exports = HelloJohn; 68 | ``` 69 | 70 | ## When Not To Use It 71 | 72 | If you prefer to declare multiple components per files you can disable this rule. 73 | -------------------------------------------------------------------------------- /tests/lib/rules/jsx-boolean-value.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Enforce boolean attributes notation in JSX 3 | * @author Yannick Croissant 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Requirements 9 | // ------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../lib/rules/jsx-boolean-value'); 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('jsx-boolean-value', rule, { 27 | valid: [ 28 | {code: ';', options: ['never'], parserOptions: parserOptions}, 29 | {code: ';', parserOptions: parserOptions}, 30 | {code: ';', options: ['always'], parserOptions: parserOptions} 31 | ], 32 | invalid: [ 33 | {code: ';', output: ';', options: ['never'], 34 | errors: [{message: 'Value must be omitted for boolean attributes'}], parserOptions: parserOptions}, 35 | {code: ';', output: ';', 36 | errors: [{message: 'Value must be omitted for boolean attributes'}], parserOptions: parserOptions}, 37 | {code: ';', output: ';', 38 | errors: [{message: 'Value must be omitted for boolean attributes'}], parserOptions: parserOptions}, 39 | {code: ';', output: ';', options: ['always'], 40 | errors: [{message: 'Value must be set for boolean attributes'}], parserOptions: parserOptions} 41 | ] 42 | }); 43 | -------------------------------------------------------------------------------- /docs/rules/jsx-first-prop-new-line.md: -------------------------------------------------------------------------------- 1 | # Configure the position of the first property (jsx-first-prop-new-line) 2 | 3 | Ensure correct position of the first property. 4 | 5 | ## Rule Details 6 | 7 | This rule checks whether the first property of all JSX elements is correctly placed. There are three possible configurations: 8 | * `always`: The first property should always be placed on a new line. 9 | * `never` : The first property should never be placed on a new line, e.g. should always be on the same line as the Component opening tag. 10 | * `multiline`: The first property should always be placed on a new line when the properties are spread over multiple lines. 11 | 12 | The following patterns are considered warnings when configured `"always"`: 13 | 14 | ```js 15 | 16 | 17 | 20 | ``` 21 | 22 | The following patterns are not considered warnings when configured `"always"`: 23 | 24 | ```js 25 | 27 | 28 | 31 | ``` 32 | 33 | The following patterns are considered warnings when configured `"never"`: 34 | 35 | ```js 36 | 38 | 39 | 42 | ``` 43 | 44 | The following patterns are not considered warnings when configured `"never"`: 45 | 46 | ```js 47 | 48 | 49 | 52 | ``` 53 | 54 | The following patterns are considered warnings when configured `"multiline"`: 55 | 56 | ```js 57 | 59 | ``` 60 | 61 | The following patterns are not considered warnings when configured `"multiline"`: 62 | 63 | ```js 64 | 65 | 66 | 70 | ``` 71 | 72 | ## When not to use 73 | 74 | If you are not using JSX then you can disable this rule. 75 | -------------------------------------------------------------------------------- /lib/rules/jsx-no-bind.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevents usage of Function.prototype.bind and arrow functions 3 | * in React component definition. 4 | * @author Daniel Lo Nigro 5 | */ 6 | 'use strict'; 7 | 8 | // ----------------------------------------------------------------------------- 9 | // Rule Definition 10 | // ----------------------------------------------------------------------------- 11 | 12 | module.exports = function(context) { 13 | var configuration = context.options[0] || {}; 14 | 15 | return { 16 | JSXAttribute: function(node) { 17 | var isRef = configuration.ignoreRefs && node.name.name === 'ref'; 18 | if (isRef || !node.value || !node.value.expression) { 19 | return; 20 | } 21 | var valueNode = node.value.expression; 22 | if ( 23 | !configuration.allowBind && 24 | valueNode.type === 'CallExpression' && 25 | valueNode.callee.type === 'MemberExpression' && 26 | valueNode.callee.property.name === 'bind' 27 | ) { 28 | context.report({ 29 | node: node, 30 | message: 'JSX props should not use .bind()' 31 | }); 32 | } else if ( 33 | !configuration.allowArrowFunctions && 34 | valueNode.type === 'ArrowFunctionExpression' 35 | ) { 36 | context.report({ 37 | node: node, 38 | message: 'JSX props should not use arrow functions' 39 | }); 40 | } 41 | } 42 | }; 43 | }; 44 | 45 | module.exports.schema = [{ 46 | type: 'object', 47 | properties: { 48 | allowArrowFunctions: { 49 | default: false, 50 | type: 'boolean' 51 | }, 52 | allowBind: { 53 | default: false, 54 | type: 'boolean' 55 | }, 56 | ignoreRefs: { 57 | default: false, 58 | type: 'boolean' 59 | } 60 | }, 61 | additionalProperties: false 62 | }]; 63 | -------------------------------------------------------------------------------- /tests/lib/rules/jsx-no-target-blank.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Forbid target='_blank' attribute 3 | * @author Kevin Miller 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Requirements 9 | // ------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../lib/rules/jsx-no-target-blank'); 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('jsx-no-target-blank', rule, { 27 | valid: [ 28 | {code: '
', parserOptions: parserOptions}, 29 | {code: '', parserOptions: parserOptions}, 30 | {code: '', parserOptions: parserOptions} 31 | ], 32 | invalid: [ 33 | {code: '', parserOptions: parserOptions, 34 | errors: [{message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' + 35 | ' see https://mathiasbynens.github.io/rel-noopener'}]}, 36 | {code: '', parserOptions: parserOptions, 37 | errors: [{message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' + 38 | ' see https://mathiasbynens.github.io/rel-noopener'}]}, 39 | {code: '', parserOptions: parserOptions, 40 | errors: [{message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' + 41 | ' see https://mathiasbynens.github.io/rel-noopener'}]} 42 | ] 43 | }); 44 | -------------------------------------------------------------------------------- /docs/rules/jsx-sort-props.md: -------------------------------------------------------------------------------- 1 | # Enforce props alphabetical sorting (jsx-sort-props) 2 | 3 | Some developers prefer to sort props names alphabetically to be able to find necessary props easier at the later time. Others feel that it adds complexity and becomes burden to maintain. 4 | 5 | ## Rule Details 6 | 7 | This rule checks all JSX components and verifies that all props are sorted alphabetically. A spread attribute resets the verification. The default configuration of the rule is case-sensitive. 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```js 12 | ; 13 | ``` 14 | 15 | The following patterns are considered okay and do not cause warnings: 16 | 17 | ```js 18 | ; 19 | ; 20 | ``` 21 | 22 | ## Rule Options 23 | 24 | ```js 25 | ... 26 | "jsx-sort-props": [, { 27 | "callbacksLast": , 28 | "shorthandFirst": , 29 | "ignoreCase": 30 | }] 31 | ... 32 | ``` 33 | 34 | ### `ignoreCase` 35 | 36 | When `true` the rule ignores the case-sensitivity of the props order. 37 | 38 | The following patterns are considered okay and do not cause warnings: 39 | 40 | ```js 41 | ; 42 | ``` 43 | 44 | ### `callbacksLast` 45 | 46 | When `true`, callbacks must be listed after all other props: 47 | 48 | ```js 49 | 50 | ``` 51 | 52 | ### `shorthandFirst` 53 | 54 | When `true`, short hand props must be listed before all other props, but still respecting the alphabetical order: 55 | 56 | ```js 57 | 58 | ``` 59 | 60 | ## When not to use 61 | 62 | This rule is a formatting preference and not following it won't negatively affect the quality of your code. If alphabetizing props isn't a part of your coding standards, then you can leave this rule off. 63 | -------------------------------------------------------------------------------- /lib/rules/no-multi-comp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent multiple component definition per file 3 | * @author Yannick Croissant 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) { 14 | 15 | var configuration = context.options[0] || {}; 16 | var ignoreStateless = configuration.ignoreStateless || false; 17 | 18 | var MULTI_COMP_MESSAGE = 'Declare only one React component per file'; 19 | 20 | /** 21 | * Checks if the component is ignored 22 | * @param {Object} component The component being checked. 23 | * @returns {Boolean} True if the component is ignored, false if not. 24 | */ 25 | function isIgnored(component) { 26 | return ignoreStateless === true && /Function/.test(component.node.type); 27 | } 28 | 29 | // -------------------------------------------------------------------------- 30 | // Public 31 | // -------------------------------------------------------------------------- 32 | 33 | return { 34 | 'Program:exit': function() { 35 | if (components.length() <= 1) { 36 | return; 37 | } 38 | 39 | var list = components.list(); 40 | var i = 0; 41 | 42 | for (var component in list) { 43 | if (!list.hasOwnProperty(component) || isIgnored(list[component]) || ++i === 1) { 44 | continue; 45 | } 46 | context.report({ 47 | node: list[component].node, 48 | message: MULTI_COMP_MESSAGE 49 | }); 50 | } 51 | } 52 | }; 53 | }); 54 | 55 | module.exports.schema = [{ 56 | type: 'object', 57 | properties: { 58 | ignoreStateless: { 59 | default: false, 60 | type: 'boolean' 61 | } 62 | }, 63 | additionalProperties: false 64 | }]; 65 | -------------------------------------------------------------------------------- /lib/rules/no-danger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent usage of dangerous JSX props 3 | * @author Scott Andrews 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Constants 9 | // ------------------------------------------------------------------------------ 10 | 11 | var DANGEROUS_MESSAGE = 'Dangerous property \'{{name}}\' found'; 12 | 13 | var DANGEROUS_PROPERTY_NAMES = [ 14 | 'dangerouslySetInnerHTML' 15 | ]; 16 | 17 | var DANGEROUS_PROPERTIES = DANGEROUS_PROPERTY_NAMES.reduce(function (props, prop) { 18 | props[prop] = prop; 19 | return props; 20 | }, Object.create(null)); 21 | 22 | // ------------------------------------------------------------------------------ 23 | // Helpers 24 | // ------------------------------------------------------------------------------ 25 | 26 | /** 27 | * Checks if a node name match the JSX tag convention. 28 | * @param {String} name - Name of the node to check. 29 | * @returns {boolean} Whether or not the node name match the JSX tag convention. 30 | */ 31 | var tagConvention = /^[a-z]|\-/; 32 | function isTagName(name) { 33 | return tagConvention.test(name); 34 | } 35 | 36 | /** 37 | * Checks if a JSX attribute is dangerous. 38 | * @param {String} name - Name of the attribute to check. 39 | * @returns {boolean} Whether or not the attribute is dnagerous. 40 | */ 41 | function isDangerous(name) { 42 | return name in DANGEROUS_PROPERTIES; 43 | } 44 | 45 | // ------------------------------------------------------------------------------ 46 | // Rule Definition 47 | // ------------------------------------------------------------------------------ 48 | 49 | module.exports = function(context) { 50 | 51 | return { 52 | 53 | JSXAttribute: function(node) { 54 | if (isTagName(node.parent.name.name) && isDangerous(node.name.name)) { 55 | context.report({ 56 | node: node, 57 | message: DANGEROUS_MESSAGE, 58 | data: { 59 | name: node.name.name 60 | } 61 | }); 62 | } 63 | } 64 | 65 | }; 66 | 67 | }; 68 | 69 | module.exports.schema = []; 70 | -------------------------------------------------------------------------------- /docs/rules/no-did-update-set-state.md: -------------------------------------------------------------------------------- 1 | # Prevent usage of setState in componentDidUpdate (no-did-update-set-state) 2 | 3 | Updating the state after a component update will trigger a second `render()` call and can lead to property/layout thrashing. 4 | 5 | ## Rule Details 6 | 7 | The following patterns are considered warnings: 8 | 9 | ```js 10 | var Hello = React.createClass({ 11 | componentDidUpdate: function() { 12 | this.setState({ 13 | name: this.props.name.toUpperCase() 14 | }); 15 | }, 16 | render: function() { 17 | return
Hello {this.state.name}
; 18 | } 19 | }); 20 | ``` 21 | 22 | The following patterns are not considered warnings: 23 | 24 | ```js 25 | var Hello = React.createClass({ 26 | componentDidUpdate: function() { 27 | this.props.onUpdate(); 28 | }, 29 | render: function() { 30 | return
Hello {this.props.name}
; 31 | } 32 | }); 33 | ``` 34 | 35 | ## Rule Options 36 | 37 | ```js 38 | ... 39 | "no-did-update-set-state": [, ] 40 | ... 41 | ``` 42 | 43 | ### `allow-in-func` mode 44 | 45 | By default this rule forbids any call to `this.setState` in `componentDidUpdate`. But in certain cases you may need to perform asynchronous calls in `componentDidUpdate` that may end up with calls to `this.setState`. The `allow-in-func` mode allows you to use `this.setState` in `componentDidUpdate` as long as they are called within a function. 46 | 47 | The following patterns are considered warnings: 48 | 49 | ```js 50 | var Hello = React.createClass({ 51 | componentDidUpdate: function() { 52 | this.setState({ 53 | name: this.props.name.toUpperCase() 54 | }); 55 | }, 56 | render: function() { 57 | return
Hello {this.state.name}
; 58 | } 59 | }); 60 | ``` 61 | 62 | The following patterns are not considered warnings: 63 | 64 | ```js 65 | var Hello = React.createClass({ 66 | componentDidUpdate: function() { 67 | this.onUpdate(function callback(newName) { 68 | this.setState({ 69 | name: newName 70 | }); 71 | }); 72 | }, 73 | render: function() { 74 | return
Hello {this.state.name}
; 75 | } 76 | }); 77 | ``` 78 | -------------------------------------------------------------------------------- /tests/lib/rules/jsx-uses-react.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for jsx-uses-react 3 | * @author Glen Mailer 4 | */ 5 | 6 | 'use strict'; 7 | 8 | // ----------------------------------------------------------------------------- 9 | // Requirements 10 | // ----------------------------------------------------------------------------- 11 | 12 | var eslint = require('eslint').linter; 13 | var rule = require('eslint/lib/rules/no-unused-vars'); 14 | var RuleTester = require('eslint').RuleTester; 15 | 16 | var parserOptions = { 17 | ecmaVersion: 6, 18 | ecmaFeatures: { 19 | jsx: true 20 | } 21 | }; 22 | 23 | var settings = { 24 | react: { 25 | pragma: 'Foo' 26 | } 27 | }; 28 | 29 | // ----------------------------------------------------------------------------- 30 | // Tests 31 | // ----------------------------------------------------------------------------- 32 | 33 | var ruleTester = new RuleTester(); 34 | eslint.defineRule('jsx-uses-react', require('../../../lib/rules/jsx-uses-react')); 35 | ruleTester.run('no-unused-vars', rule, { 36 | valid: [ 37 | {code: '/*eslint jsx-uses-react:1*/ var React;
;', parserOptions: parserOptions}, 38 | {code: '/*eslint jsx-uses-react:1*/ var React; (function () {
})();', parserOptions: parserOptions}, 39 | {code: '/*eslint jsx-uses-react:1*/ /** @jsx Foo */ var Foo;
;', parserOptions: parserOptions}, 40 | {code: '/*eslint jsx-uses-react:[1,{"pragma":"Foo"}]*/ var Foo;
;', parserOptions: parserOptions}, 41 | {code: '/*eslint jsx-uses-react:1*/ var Foo;
;', settings: settings, parserOptions: parserOptions} 42 | ], 43 | invalid: [ 44 | {code: '/*eslint jsx-uses-react:1*/ var React;', 45 | errors: [{message: '\'React\' is defined but never used'}], parserOptions: parserOptions}, 46 | {code: '/*eslint jsx-uses-react:1*/ /** @jsx Foo */ var React;
;', 47 | errors: [{message: '\'React\' is defined but never used'}], parserOptions: parserOptions}, 48 | {code: '/*eslint jsx-uses-react:1*/ var React;
;', 49 | errors: [{message: '\'React\' is defined but never used'}], settings: settings, parserOptions: parserOptions} 50 | ] 51 | }); 52 | -------------------------------------------------------------------------------- /lib/rules/jsx-pascal-case.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Enforce PasalCase for user-defined JSX components 3 | * @author Jake Marsh 4 | */ 5 | 6 | 'use strict'; 7 | 8 | // ------------------------------------------------------------------------------ 9 | // Constants 10 | // ------------------------------------------------------------------------------ 11 | 12 | var PASCAL_CASE_REGEX = /^([A-Z0-9]|[A-Z0-9]+[a-z0-9]+(?:[A-Z0-9]+[a-z0-9]*)*)$/; 13 | var COMPAT_TAG_REGEX = /^[a-z]|\-/; 14 | var ALL_CAPS_TAG_REGEX = /^[A-Z0-9]+$/; 15 | 16 | // ------------------------------------------------------------------------------ 17 | // Rule Definition 18 | // ------------------------------------------------------------------------------ 19 | 20 | module.exports = function(context) { 21 | 22 | var configuration = context.options[0] || {}; 23 | var allowAllCaps = configuration.allowAllCaps || false; 24 | var ignore = configuration.ignore || []; 25 | 26 | return { 27 | JSXOpeningElement: function(node) { 28 | switch (node.name.type) { 29 | case 'JSXIdentifier': 30 | node = node.name; 31 | break; 32 | case 'JSXMemberExpression': 33 | node = node.name.object; 34 | break; 35 | case 'JSXNamespacedName': 36 | node = node.name.namespace; 37 | break; 38 | default: 39 | break; 40 | } 41 | 42 | var isPascalCase = PASCAL_CASE_REGEX.test(node.name); 43 | var isCompatTag = COMPAT_TAG_REGEX.test(node.name); 44 | var isAllowedAllCaps = allowAllCaps && ALL_CAPS_TAG_REGEX.test(node.name); 45 | var isIgnored = ignore.indexOf(node.name) !== -1; 46 | 47 | if (!isPascalCase && !isCompatTag && !isAllowedAllCaps && !isIgnored) { 48 | context.report({ 49 | node: node, 50 | message: 'Imported JSX component ' + node.name + ' must be in PascalCase' 51 | }); 52 | } 53 | } 54 | }; 55 | 56 | }; 57 | 58 | module.exports.schema = [{ 59 | type: 'object', 60 | properties: { 61 | allowAllCaps: { 62 | type: 'boolean' 63 | }, 64 | ignore: { 65 | type: 'array' 66 | } 67 | }, 68 | additionalProperties: false 69 | }]; 70 | -------------------------------------------------------------------------------- /lib/rules/jsx-handler-names.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Enforce event handler naming conventions in JSX 3 | * @author Jake Marsh 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Rule Definition 9 | // ------------------------------------------------------------------------------ 10 | 11 | module.exports = function(context) { 12 | 13 | var sourceCode = context.getSourceCode(); 14 | var configuration = context.options[0] || {}; 15 | var eventHandlerPrefix = configuration.eventHandlerPrefix || 'handle'; 16 | var eventHandlerPropPrefix = configuration.eventHandlerPropPrefix || 'on'; 17 | 18 | var EVENT_HANDLER_REGEX = new RegExp('^((props\\.' + eventHandlerPropPrefix + ')' 19 | + '|((.*\\.)?' + eventHandlerPrefix + ')).+$'); 20 | var PROP_EVENT_HANDLER_REGEX = new RegExp('^(' + eventHandlerPropPrefix + '.+|ref)$'); 21 | 22 | return { 23 | JSXAttribute: function(node) { 24 | if (!node.value || !node.value.expression || !node.value.expression.object) { 25 | return; 26 | } 27 | 28 | var propKey = typeof node.name === 'object' ? node.name.name : node.name; 29 | var propValue = sourceCode.getText(node.value.expression).replace(/^this\./, ''); 30 | 31 | if (propKey === 'ref') { 32 | return; 33 | } 34 | 35 | var propIsEventHandler = PROP_EVENT_HANDLER_REGEX.test(propKey); 36 | var propFnIsNamedCorrectly = EVENT_HANDLER_REGEX.test(propValue); 37 | 38 | if (propIsEventHandler && !propFnIsNamedCorrectly) { 39 | context.report({ 40 | node: node, 41 | message: 'Handler function for ' + propKey + ' prop key must begin with \'' + eventHandlerPrefix + '\'' 42 | }); 43 | } else if (propFnIsNamedCorrectly && !propIsEventHandler) { 44 | context.report({ 45 | node: node, 46 | message: 'Prop key for ' + propValue + ' must begin with \'' + eventHandlerPropPrefix + '\'' 47 | }); 48 | } 49 | } 50 | }; 51 | 52 | }; 53 | 54 | module.exports.schema = [{ 55 | type: 'object', 56 | properties: { 57 | eventHandlerPrefix: { 58 | type: 'string' 59 | }, 60 | eventHandlerPropPrefix: { 61 | type: 'string' 62 | } 63 | }, 64 | additionalProperties: false 65 | }]; 66 | -------------------------------------------------------------------------------- /tests/lib/rules/self-closing-comp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent extra closing tags for components without children 3 | * @author Yannick Croissant 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Requirements 9 | // ------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../lib/rules/self-closing-comp'); 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('self-closing-comp', rule, { 27 | 28 | valid: [ 29 | { 30 | code: 'var contentContainer =
;', 31 | parserOptions: parserOptions 32 | }, { 33 | code: 'var HelloJohn = ;', 34 | parserOptions: parserOptions 35 | }, { 36 | code: 'var Profile = ;', 37 | parserOptions: parserOptions 38 | }, { 39 | code: '\ 40 | \ 41 | \ 42 | ', 43 | parserOptions: parserOptions 44 | }, { 45 | code: 'var HelloJohn =
 
;', 46 | parserOptions: parserOptions 47 | }, { 48 | code: 'var HelloJohn =
{\' \'}
;', 49 | parserOptions: parserOptions 50 | }, { 51 | code: 'var HelloJohn =  ;', 52 | parserOptions: parserOptions 53 | } 54 | ], 55 | 56 | invalid: [ 57 | { 58 | code: 'var HelloJohn = ;', 59 | parserOptions: parserOptions, 60 | errors: [{ 61 | message: 'Empty components are self-closing' 62 | }] 63 | }, { 64 | code: 'var HelloJohn = \n;', 65 | parserOptions: parserOptions, 66 | errors: [{ 67 | message: 'Empty components are self-closing' 68 | }] 69 | }, { 70 | code: 'var HelloJohn = ;', 71 | parserOptions: parserOptions, 72 | errors: [{ 73 | message: 'Empty components are self-closing' 74 | }] 75 | } 76 | ] 77 | }); 78 | -------------------------------------------------------------------------------- /lib/rules/require-extension.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Restrict file extensions that may be required 3 | * @author Scott Andrews 4 | */ 5 | 'use strict'; 6 | 7 | var path = require('path'); 8 | 9 | // ------------------------------------------------------------------------------ 10 | // Constants 11 | // ------------------------------------------------------------------------------ 12 | 13 | var DEFAULTS = { 14 | extensions: ['.jsx'] 15 | }; 16 | 17 | var PKG_REGEX = /^[^\.]((?!\/).)*$/; 18 | 19 | // ------------------------------------------------------------------------------ 20 | // Rule Definition 21 | // ------------------------------------------------------------------------------ 22 | 23 | module.exports = function(context) { 24 | 25 | function isPackage(id) { 26 | return PKG_REGEX.test(id); 27 | } 28 | 29 | function isRequire(expression) { 30 | return expression.callee.name === 'require'; 31 | } 32 | 33 | function getId(expression) { 34 | return expression.arguments[0] && expression.arguments[0].value; 35 | } 36 | 37 | function getExtension(id) { 38 | return path.extname(id || ''); 39 | } 40 | 41 | function getExtensionsConfig() { 42 | return context.options[0] && context.options[0].extensions || DEFAULTS.extensions; 43 | } 44 | 45 | var forbiddenExtensions = getExtensionsConfig().reduce(function (extensions, extension) { 46 | extensions[extension] = true; 47 | return extensions; 48 | }, Object.create(null)); 49 | 50 | function isForbiddenExtension(ext) { 51 | return ext in forbiddenExtensions; 52 | } 53 | 54 | // -------------------------------------------------------------------------- 55 | // Public 56 | // -------------------------------------------------------------------------- 57 | 58 | return { 59 | 60 | CallExpression: function(node) { 61 | if (isRequire(node)) { 62 | var id = getId(node); 63 | var ext = getExtension(id); 64 | if (!isPackage(id) && isForbiddenExtension(ext)) { 65 | context.report({ 66 | node: node, 67 | message: 'Unable to require module with extension \'' + ext + '\'' 68 | }); 69 | } 70 | } 71 | } 72 | 73 | }; 74 | 75 | }; 76 | 77 | module.exports.schema = [{ 78 | type: 'object', 79 | properties: { 80 | extensions: { 81 | type: 'array', 82 | items: { 83 | type: 'string' 84 | } 85 | } 86 | }, 87 | additionalProperties: false 88 | }]; 89 | -------------------------------------------------------------------------------- /tests/lib/rules/jsx-pascal-case.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for jsx-pascal-case 3 | * @author Jake Marsh 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Requirements 9 | // ------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../lib/rules/jsx-pascal-case'); 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('jsx-pascal-case', rule, { 27 | valid: [{ 28 | code: '', 29 | parserOptions: parserOptions 30 | }, { 31 | code: '', 32 | parserOptions: parserOptions 33 | }, { 34 | code: '', 35 | parserOptions: parserOptions 36 | }, { 37 | code: '', 38 | parserOptions: parserOptions 39 | }, { 40 | code: '', 41 | parserOptions: parserOptions 42 | }, { 43 | code: '
', 44 | parserOptions: parserOptions 45 | }, { 46 | code: '', 47 | parserOptions: parserOptions 48 | }, { 49 | code: '', 50 | parserOptions: parserOptions 51 | }, { 52 | code: '', 53 | parserOptions: parserOptions 54 | }, { 55 | code: '', 56 | parserOptions: parserOptions 57 | }, { 58 | code: '', 59 | parserOptions: parserOptions, 60 | options: [{allowAllCaps: true}] 61 | }, { 62 | code: '', 63 | parserOptions: parserOptions, 64 | options: [{ignore: ['IGNORED']}] 65 | }], 66 | 67 | invalid: [{ 68 | code: '', 69 | parserOptions: parserOptions, 70 | errors: [{message: 'Imported JSX component Test_component must be in PascalCase'}] 71 | }, { 72 | code: '', 73 | parserOptions: parserOptions, 74 | errors: [{message: 'Imported JSX component TEST_COMPONENT must be in PascalCase'}] 75 | }, { 76 | code: '', 77 | parserOptions: parserOptions, 78 | errors: [{message: 'Imported JSX component YMCA must be in PascalCase'}] 79 | }] 80 | }); 81 | -------------------------------------------------------------------------------- /lib/rules/jsx-key.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Report missing `key` props in iterators/collection literals. 3 | * @author Ben Mosher 4 | */ 5 | 'use strict'; 6 | 7 | // var Components = require('../util/Components'); 8 | 9 | // ------------------------------------------------------------------------------ 10 | // Rule Definition 11 | // ------------------------------------------------------------------------------ 12 | 13 | module.exports = function(context) { 14 | 15 | function hasKeyProp(node) { 16 | return node.openingElement.attributes.some(function(decl) { 17 | if (decl.type === 'JSXSpreadAttribute') { 18 | return false; 19 | } 20 | return (decl.name.name === 'key'); 21 | }); 22 | } 23 | 24 | function checkIteratorElement(node) { 25 | if (node.type === 'JSXElement' && !hasKeyProp(node)) { 26 | context.report({ 27 | node: node, 28 | message: 'Missing "key" prop for element in iterator' 29 | }); 30 | } 31 | } 32 | 33 | function getReturnStatement(body) { 34 | return body.filter(function(item) { 35 | return item.type === 'ReturnStatement'; 36 | })[0]; 37 | } 38 | 39 | return { 40 | JSXElement: function(node) { 41 | if (hasKeyProp(node)) { 42 | return; 43 | } 44 | 45 | if (node.parent.type === 'ArrayExpression') { 46 | context.report({ 47 | node: node, 48 | message: 'Missing "key" prop for element in array' 49 | }); 50 | } 51 | }, 52 | 53 | // Array.prototype.map 54 | CallExpression: function (node) { 55 | if (node.callee && node.callee.type !== 'MemberExpression') { 56 | return; 57 | } 58 | 59 | if (node.callee && node.callee.property && node.callee.property.name !== 'map') { 60 | return; 61 | } 62 | 63 | var fn = node.arguments[0]; 64 | var isFn = fn && fn.type === 'FunctionExpression'; 65 | var isArrFn = fn && fn.type === 'ArrowFunctionExpression'; 66 | 67 | if (isArrFn && fn.body.type === 'JSXElement') { 68 | checkIteratorElement(fn.body); 69 | } 70 | 71 | if (isFn || isArrFn) { 72 | if (fn.body.type === 'BlockStatement') { 73 | var returnStatement = getReturnStatement(fn.body.body); 74 | if (returnStatement && returnStatement.argument) { 75 | checkIteratorElement(returnStatement.argument); 76 | } 77 | } 78 | } 79 | } 80 | }; 81 | }; 82 | 83 | module.exports.schema = []; 84 | -------------------------------------------------------------------------------- /docs/rules/no-did-mount-set-state.md: -------------------------------------------------------------------------------- 1 | # Prevent usage of setState in componentDidMount (no-did-mount-set-state) 2 | 3 | Updating the state after a component mount will trigger a second `render()` call and can lead to property/layout thrashing. 4 | 5 | ## Rule Details 6 | 7 | This rule is aimed to forbid the use of `this.setState` in `componentDidMount`. 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```js 12 | var Hello = React.createClass({ 13 | componentDidMount: function() { 14 | this.setState({ 15 | name: this.props.name.toUpperCase() 16 | }); 17 | }, 18 | render: function() { 19 | return
Hello {this.state.name}
; 20 | } 21 | }); 22 | ``` 23 | 24 | ```js 25 | var Hello = React.createClass({ 26 | componentDidMount: function() { 27 | this.onMount(function callback(newName) { 28 | this.setState({ 29 | name: newName 30 | }); 31 | }); 32 | }, 33 | render: function() { 34 | return
Hello {this.state.name}
; 35 | } 36 | }); 37 | ``` 38 | 39 | The following patterns are not considered warnings: 40 | 41 | ```js 42 | var Hello = React.createClass({ 43 | componentDidMount: function() { 44 | this.props.onMount(); 45 | }, 46 | render: function() { 47 | return
Hello {this.props.name}
; 48 | } 49 | }); 50 | ``` 51 | 52 | ## Rule Options 53 | 54 | ```js 55 | ... 56 | "no-did-mount-set-state": [, ] 57 | ... 58 | ``` 59 | 60 | ### `allow-in-func` mode 61 | 62 | By default this rule forbids any call to `this.setState` in `componentDidMount`. But since `componentDidMount` is a common place to set some event listeners, you may end up with calls to `this.setState` in some callbacks. The `allow-in-func` mode allows you to use `this.setState` in `componentDidMount` as long as they are called within a function. 63 | 64 | The following patterns are considered warnings: 65 | 66 | ```js 67 | var Hello = React.createClass({ 68 | componentDidMount: function() { 69 | this.setState({ 70 | name: this.props.name.toUpperCase() 71 | }); 72 | }, 73 | render: function() { 74 | return
Hello {this.state.name}
; 75 | } 76 | }); 77 | ``` 78 | 79 | The following patterns are not considered warnings: 80 | 81 | ```js 82 | var Hello = React.createClass({ 83 | componentDidMount: function() { 84 | this.onMount(function callback(newName) { 85 | this.setState({ 86 | name: newName 87 | }); 88 | }); 89 | }, 90 | render: function() { 91 | return
Hello {this.state.name}
; 92 | } 93 | }); 94 | ``` 95 | -------------------------------------------------------------------------------- /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 | var Components = require('../util/Components'); 8 | 9 | // ------------------------------------------------------------------------------ 10 | // Rule Definition 11 | // ------------------------------------------------------------------------------ 12 | 13 | module.exports = Components.detect(function(context, components, utils) { 14 | 15 | /** 16 | * Checks if the component is valid 17 | * @param {Object} component The component to process 18 | * @returns {Boolean} True if the component is valid, false if not. 19 | */ 20 | function isValid(component) { 21 | return Boolean(component && !component.mutateSetState); 22 | } 23 | 24 | /** 25 | * Reports undeclared proptypes for a given component 26 | * @param {Object} component The component to process 27 | */ 28 | function reportMutations(component) { 29 | var mutation; 30 | for (var i = 0, j = component.mutations.length; i < j; i++) { 31 | mutation = component.mutations[i]; 32 | context.report({ 33 | node: mutation, 34 | message: 'Do not mutate state directly. Use setState().' 35 | }); 36 | } 37 | } 38 | 39 | // -------------------------------------------------------------------------- 40 | // Public 41 | // -------------------------------------------------------------------------- 42 | 43 | return { 44 | AssignmentExpression: function(node) { 45 | var item; 46 | if (!node.left || !node.left.object || !node.left.object.object) { 47 | return; 48 | } 49 | item = node.left.object; 50 | while (item.object.property) { 51 | item = item.object; 52 | } 53 | if ( 54 | item.object.type === 'ThisExpression' && 55 | item.property.name === 'state' 56 | ) { 57 | var component = components.get(utils.getParentComponent()); 58 | var mutations = component && component.mutations || []; 59 | mutations.push(node.left.object); 60 | components.set(node, { 61 | mutateSetState: true, 62 | mutations: mutations 63 | }); 64 | } 65 | }, 66 | 67 | 'Program:exit': function() { 68 | var list = components.list(); 69 | for (var component in list) { 70 | if (!list.hasOwnProperty(component) || isValid(list[component])) { 71 | continue; 72 | } 73 | reportMutations(list[component]); 74 | } 75 | } 76 | }; 77 | 78 | }); 79 | -------------------------------------------------------------------------------- /lib/rules/jsx-space-before-closing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Validate spacing before closing bracket in JSX. 3 | * @author ryym 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Rule Definition 9 | // ------------------------------------------------------------------------------ 10 | 11 | module.exports = function(context) { 12 | 13 | var configuration = context.options[0] || 'always'; 14 | var sourceCode = context.getSourceCode(); 15 | 16 | var NEVER_MESSAGE = 'A space is forbidden before closing bracket'; 17 | var ALWAYS_MESSAGE = 'A space is required before closing bracket'; 18 | 19 | /** 20 | * Find the token before the closing bracket. 21 | * @param {ASTNode} node - The JSX element node. 22 | * @returns {Token} The token before the closing bracket. 23 | */ 24 | function getTokenBeforeClosingBracket(node) { 25 | var attributes = node.attributes; 26 | if (attributes.length === 0) { 27 | return node.name; 28 | } 29 | return attributes[ attributes.length - 1 ]; 30 | } 31 | 32 | // -------------------------------------------------------------------------- 33 | // Public 34 | // -------------------------------------------------------------------------- 35 | 36 | return { 37 | JSXOpeningElement: function(node) { 38 | if (!node.selfClosing) { 39 | return; 40 | } 41 | 42 | var leftToken = getTokenBeforeClosingBracket(node); 43 | var closingSlash = sourceCode.getTokenAfter(leftToken); 44 | 45 | if (leftToken.loc.end.line !== closingSlash.loc.start.line) { 46 | return; 47 | } 48 | 49 | if (configuration === 'always' && !sourceCode.isSpaceBetweenTokens(leftToken, closingSlash)) { 50 | context.report({ 51 | loc: closingSlash.loc.start, 52 | message: ALWAYS_MESSAGE, 53 | fix: function(fixer) { 54 | return fixer.insertTextBefore(closingSlash, ' '); 55 | } 56 | }); 57 | } else if (configuration === 'never' && sourceCode.isSpaceBetweenTokens(leftToken, closingSlash)) { 58 | context.report({ 59 | loc: closingSlash.loc.start, 60 | message: NEVER_MESSAGE, 61 | fix: function(fixer) { 62 | var previousToken = sourceCode.getTokenBefore(closingSlash); 63 | return fixer.removeRange([previousToken.range[1], closingSlash.range[0]]); 64 | } 65 | }); 66 | } 67 | } 68 | }; 69 | 70 | }; 71 | 72 | module.exports.schema = [{ 73 | enum: ['always', 'never'] 74 | }]; 75 | -------------------------------------------------------------------------------- /tests/lib/rules/jsx-no-duplicate-props.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Enforce no duplicate props 3 | * @author Markus Ånöstam 4 | */ 5 | 6 | 'use strict'; 7 | 8 | // ----------------------------------------------------------------------------- 9 | // Requirements 10 | // ----------------------------------------------------------------------------- 11 | 12 | var rule = require('../../../lib/rules/jsx-no-duplicate-props'); 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 | 28 | var expectedError = { 29 | message: 'No duplicate props allowed', 30 | type: 'JSXAttribute' 31 | }; 32 | 33 | var ignoreCaseArgs = [{ 34 | ignoreCase: true 35 | }]; 36 | 37 | ruleTester.run('jsx-no-duplicate-props', rule, { 38 | valid: [ 39 | {code: ';', parserOptions: parserOptions}, 40 | {code: ';', parserOptions: parserOptions}, 41 | {code: ';', parserOptions: parserOptions}, 42 | {code: ';', parserOptions: parserOptions}, 43 | {code: ';', parserOptions: parserOptions}, 44 | {code: ';', parserOptions: parserOptions}, 45 | {code: ';', parserOptions: parserOptions}, 46 | {code: ';', parserOptions: parserOptions}, 47 | {code: ';', parserOptions: parserOptions}, 48 | {code: ';', parserOptions: parserOptions}, 49 | {code: ';', parserOptions: parserOptions}, 50 | {code: ';', parserOptions: parserOptions} 51 | ], 52 | invalid: [ 53 | {code: ';', errors: [expectedError], parserOptions: parserOptions}, 54 | {code: ';', errors: [expectedError], parserOptions: parserOptions}, 55 | {code: ';', errors: [expectedError], parserOptions: parserOptions}, 56 | {code: ';', options: ignoreCaseArgs, errors: [expectedError], parserOptions: parserOptions}, 57 | {code: ';', options: ignoreCaseArgs, errors: [expectedError], parserOptions: parserOptions}, 58 | {code: ';', options: ignoreCaseArgs, errors: [expectedError], parserOptions: parserOptions} 59 | ] 60 | }); 61 | -------------------------------------------------------------------------------- /lib/rules/jsx-no-undef.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Disallow undeclared variables in JSX 3 | * @author Yannick Croissant 4 | */ 5 | 6 | 'use strict'; 7 | 8 | /** 9 | * Checks if a node name match the JSX tag convention. 10 | * @param {String} name - Name of the node to check. 11 | * @returns {boolean} Whether or not the node name match the JSX tag convention. 12 | */ 13 | var tagConvention = /^[a-z]|\-/; 14 | function isTagName(name) { 15 | return tagConvention.test(name); 16 | } 17 | 18 | // ------------------------------------------------------------------------------ 19 | // Rule Definition 20 | // ------------------------------------------------------------------------------ 21 | 22 | module.exports = function(context) { 23 | 24 | /** 25 | * Compare an identifier with the variables declared in the scope 26 | * @param {ASTNode} node - Identifier or JSXIdentifier node 27 | * @returns {void} 28 | */ 29 | function checkIdentifierInJSX(node) { 30 | var scope = context.getScope(); 31 | var variables = scope.variables; 32 | var i; 33 | var len; 34 | 35 | // Ignore 'this' keyword (also maked as JSXIdentifier when used in JSX) 36 | if (node.name === 'this') { 37 | return; 38 | } 39 | 40 | while (scope.type !== 'global') { 41 | scope = scope.upper; 42 | variables = scope.variables.concat(variables); 43 | } 44 | if (scope.childScopes.length) { 45 | variables = scope.childScopes[0].variables.concat(variables); 46 | // Temporary fix for babel-eslint 47 | if (scope.childScopes[0].childScopes.length) { 48 | variables = scope.childScopes[0].childScopes[0].variables.concat(variables); 49 | } 50 | } 51 | 52 | for (i = 0, len = variables.length; i < len; i++) { 53 | if (variables[i].name === node.name) { 54 | return; 55 | } 56 | } 57 | 58 | context.report({ 59 | node: node, 60 | message: '\'' + node.name + '\' is not defined.' 61 | }); 62 | } 63 | 64 | return { 65 | JSXOpeningElement: function(node) { 66 | switch (node.name.type) { 67 | case 'JSXIdentifier': 68 | node = node.name; 69 | if (isTagName(node.name)) { 70 | return; 71 | } 72 | break; 73 | case 'JSXMemberExpression': 74 | node = node.name; 75 | do { 76 | node = node.object; 77 | } while (node && node.type !== 'JSXIdentifier'); 78 | break; 79 | case 'JSXNamespacedName': 80 | node = node.name.namespace; 81 | break; 82 | default: 83 | break; 84 | } 85 | checkIdentifierInJSX(node); 86 | } 87 | }; 88 | 89 | }; 90 | 91 | module.exports.schema = []; 92 | -------------------------------------------------------------------------------- /tests/lib/rules/jsx-key.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Report missing `key` props in iterators/collection literals. 3 | * @author Ben Mosher 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Requirements 9 | // ------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../lib/rules/jsx-key'); 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('jsx-key', rule, { 27 | valid: [ 28 | {code: 'fn()', parserOptions: parserOptions}, 29 | {code: '[1, 2, 3].map(function () {})', parserOptions: parserOptions}, 30 | {code: ';', parserOptions: parserOptions}, 31 | {code: '[, ];', parserOptions: parserOptions}, 32 | {code: '[1, 2, 3].map(function(x) { return });', parserOptions: parserOptions}, 33 | {code: '[1, 2, 3].map(x => );', parserOptions: parserOptions}, 34 | {code: '[1, 2, 3].map(x => { return });', parserOptions: parserOptions}, 35 | {code: '[1, 2, 3].foo(x => );', parserOptions: parserOptions}, 36 | {code: 'var App = () =>
;', parserOptions: parserOptions}, 37 | {code: '[1, 2, 3].map(function(x) { return; });', parserOptions: parserOptions}, 38 | {code: 'foo(() =>
);', parserOptions: parserOptions} 39 | ], 40 | invalid: [ 41 | {code: '[];', 42 | errors: [{message: 'Missing "key" prop for element in array'}], 43 | parserOptions: parserOptions}, 44 | 45 | {code: '[];', 46 | errors: [{message: 'Missing "key" prop for element in array'}], 47 | parserOptions: parserOptions}, 48 | 49 | {code: '[, ];', 50 | errors: [{message: 'Missing "key" prop for element in array'}], 51 | parserOptions: parserOptions}, 52 | 53 | {code: '[1, 2 ,3].map(function(x) { return });', 54 | errors: [{message: 'Missing "key" prop for element in iterator'}], 55 | parserOptions: parserOptions}, 56 | 57 | {code: '[1, 2 ,3].map(x => );', 58 | errors: [{message: 'Missing "key" prop for element in iterator'}], 59 | parserOptions: parserOptions}, 60 | 61 | {code: '[1, 2 ,3].map(x => { return });', 62 | errors: [{message: 'Missing "key" prop for element in iterator'}], 63 | parserOptions: parserOptions} 64 | ] 65 | }); 66 | -------------------------------------------------------------------------------- /lib/util/variable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Utility functions for React components detection 3 | * @author Yannick Croissant 4 | */ 5 | 'use strict'; 6 | 7 | /** 8 | * Record that a particular variable has been used in code 9 | * 10 | * @param {Object} context The current rule context. 11 | * @param {String} name The name of the variable to mark as used. 12 | * @returns {Boolean} True if the variable was found and marked as used, false if not. 13 | */ 14 | function markVariableAsUsed(context, name) { 15 | var scope = context.getScope(); 16 | var variables; 17 | var i; 18 | var len; 19 | var found = false; 20 | 21 | // Special Node.js scope means we need to start one level deeper 22 | if (scope.type === 'global') { 23 | while (scope.childScopes.length) { 24 | scope = scope.childScopes[0]; 25 | } 26 | } 27 | 28 | do { 29 | variables = scope.variables; 30 | for (i = 0, len = variables.length; i < len; i++) { 31 | if (variables[i].name === name) { 32 | variables[i].eslintUsed = true; 33 | found = true; 34 | } 35 | } 36 | scope = scope.upper; 37 | } while (scope); 38 | 39 | return found; 40 | } 41 | 42 | /** 43 | * Search a particular variable in a list 44 | * @param {Array} variables The variables list. 45 | * @param {Array} name The name of the variable to search. 46 | * @returns {Boolean} True if the variable was found, false if not. 47 | */ 48 | function findVariable(variables, name) { 49 | var i; 50 | var len; 51 | 52 | for (i = 0, len = variables.length; i < len; i++) { 53 | if (variables[i].name === name) { 54 | return true; 55 | } 56 | } 57 | 58 | return false; 59 | } 60 | 61 | /** 62 | * List all variable in a given scope 63 | * 64 | * Contain a patch for babel-eslint to avoid https://github.com/babel/babel-eslint/issues/21 65 | * 66 | * @param {Object} context The current rule context. 67 | * @param {Array} name The name of the variable to search. 68 | * @returns {Boolean} True if the variable was found, false if not. 69 | */ 70 | function variablesInScope(context) { 71 | var scope = context.getScope(); 72 | var variables = scope.variables; 73 | 74 | while (scope.type !== 'global') { 75 | scope = scope.upper; 76 | variables = scope.variables.concat(variables); 77 | } 78 | if (scope.childScopes.length) { 79 | variables = scope.childScopes[0].variables.concat(variables); 80 | if (scope.childScopes[0].childScopes.length) { 81 | variables = scope.childScopes[0].childScopes[0].variables.concat(variables); 82 | } 83 | } 84 | 85 | return variables; 86 | } 87 | 88 | module.exports = { 89 | findVariable: findVariable, 90 | variablesInScope: variablesInScope, 91 | markVariableAsUsed: markVariableAsUsed 92 | }; 93 | -------------------------------------------------------------------------------- /tests/lib/rules/jsx-max-props-per-line.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Limit maximum of props on a single line in JSX 3 | * @author Yannick Croissant 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Requirements 9 | // ------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../lib/rules/jsx-max-props-per-line'); 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('jsx-max-props-per-line', rule, { 27 | valid: [{ 28 | code: '', 29 | parserOptions: parserOptions 30 | }, { 31 | code: '', 32 | options: [{maximum: 2}], 33 | parserOptions: parserOptions 34 | }, { 35 | code: '', 36 | options: [{maximum: 2}], 37 | parserOptions: parserOptions 38 | }, { 39 | code: [ 40 | '' 44 | ].join('\n'), 45 | parserOptions: parserOptions 46 | }, { 47 | code: [ 48 | '' 52 | ].join('\n'), 53 | options: [{maximum: 2}], 54 | parserOptions: parserOptions 55 | }], 56 | 57 | invalid: [{ 58 | code: ';', 59 | errors: [{message: 'Prop `bar` must be placed on a new line'}], 60 | parserOptions: parserOptions 61 | }, { 62 | code: ';', 63 | options: [{maximum: 2}], 64 | errors: [{message: 'Prop `baz` must be placed on a new line'}], 65 | parserOptions: parserOptions 66 | }, { 67 | code: ';', 68 | errors: [{message: 'Prop `bar` must be placed on a new line'}], 69 | parserOptions: parserOptions 70 | }, { 71 | code: ';', 72 | errors: [{message: 'Prop `this.props` must be placed on a new line'}], 73 | parserOptions: parserOptions 74 | }, { 75 | code: [ 76 | '' 80 | ].join('\n'), 81 | errors: [{message: 'Prop `bar` must be placed on a new line'}], 82 | parserOptions: parserOptions 83 | }, { 84 | code: [ 85 | '' 89 | ].join('\n'), 90 | errors: [{message: 'Prop `this.props` must be placed on a new line'}], 91 | parserOptions: parserOptions 92 | }] 93 | }); 94 | -------------------------------------------------------------------------------- /lib/rules/wrap-multilines.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent missing parentheses around multilines JSX 3 | * @author Yannick Croissant 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Constants 9 | // ------------------------------------------------------------------------------ 10 | 11 | var DEFAULTS = { 12 | declaration: true, 13 | assignment: true, 14 | return: true 15 | }; 16 | 17 | // ------------------------------------------------------------------------------ 18 | // Rule Definition 19 | // ------------------------------------------------------------------------------ 20 | 21 | module.exports = function(context) { 22 | 23 | var sourceCode = context.getSourceCode(); 24 | 25 | function isParenthesised(node) { 26 | var previousToken = sourceCode.getTokenBefore(node); 27 | var nextToken = sourceCode.getTokenAfter(node); 28 | 29 | return previousToken && nextToken && 30 | previousToken.value === '(' && previousToken.range[1] <= node.range[0] && 31 | nextToken.value === ')' && nextToken.range[0] >= node.range[1]; 32 | } 33 | 34 | function isMultilines(node) { 35 | return node.loc.start.line !== node.loc.end.line; 36 | } 37 | 38 | function check(node) { 39 | if (!node || node.type !== 'JSXElement') { 40 | return; 41 | } 42 | 43 | if (!isParenthesised(node) && isMultilines(node)) { 44 | context.report({ 45 | node: node, 46 | message: 'Missing parentheses around multilines JSX', 47 | fix: function(fixer) { 48 | return fixer.replaceText(node, '(' + sourceCode.getText(node) + ')'); 49 | } 50 | }); 51 | } 52 | } 53 | 54 | function isEnabled(type) { 55 | var userOptions = context.options[0] || {}; 56 | if (({}).hasOwnProperty.call(userOptions, type)) { 57 | return userOptions[type]; 58 | } 59 | return DEFAULTS[type]; 60 | } 61 | 62 | // -------------------------------------------------------------------------- 63 | // Public 64 | // -------------------------------------------------------------------------- 65 | 66 | return { 67 | 68 | VariableDeclarator: function(node) { 69 | if (isEnabled('declaration')) { 70 | check(node.init); 71 | } 72 | }, 73 | 74 | AssignmentExpression: function(node) { 75 | if (isEnabled('assignment')) { 76 | check(node.right); 77 | } 78 | }, 79 | 80 | ReturnStatement: function(node) { 81 | if (isEnabled('return')) { 82 | check(node.argument); 83 | } 84 | } 85 | }; 86 | 87 | }; 88 | 89 | module.exports.schema = [{ 90 | type: 'object', 91 | properties: { 92 | declaration: { 93 | type: 'boolean' 94 | }, 95 | assignment: { 96 | type: 'boolean' 97 | }, 98 | return: { 99 | type: 'boolean' 100 | } 101 | }, 102 | additionalProperties: false 103 | }]; 104 | -------------------------------------------------------------------------------- /tests/lib/rules/jsx-no-undef.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for jsx-no-undef 3 | * @author Yannick Croissant 4 | */ 5 | 6 | 'use strict'; 7 | 8 | // ----------------------------------------------------------------------------- 9 | // Requirements 10 | // ----------------------------------------------------------------------------- 11 | 12 | var eslint = require('eslint').linter; 13 | var rule = require('../../../lib/rules/jsx-no-undef'); 14 | var RuleTester = require('eslint').RuleTester; 15 | 16 | var parserOptions = { 17 | ecmaVersion: 6, 18 | ecmaFeatures: { 19 | jsx: true 20 | } 21 | }; 22 | 23 | // ----------------------------------------------------------------------------- 24 | // Tests 25 | // ----------------------------------------------------------------------------- 26 | 27 | var ruleTester = new RuleTester(); 28 | eslint.defineRule('no-undef', require('eslint/lib/rules/no-undef')); 29 | ruleTester.run('jsx-no-undef', rule, { 30 | valid: [{ 31 | code: '/*eslint no-undef:1*/ var React, App; React.render();', 32 | parserOptions: parserOptions 33 | }, { 34 | code: '/*eslint no-undef:1*/ var React, App; React.render();', 35 | parserOptions: parserOptions 36 | }, { 37 | code: '/*eslint no-undef:1*/ var React; React.render();', 38 | parserOptions: parserOptions 39 | }, { 40 | code: '/*eslint no-undef:1*/ var React; React.render();', 41 | parserOptions: parserOptions 42 | }, { 43 | code: '/*eslint no-undef:1*/ var React, app; React.render();', 44 | parserOptions: parserOptions 45 | }, { 46 | code: '/*eslint no-undef:1*/ var React, app; React.render();', 47 | parserOptions: parserOptions 48 | }, { 49 | code: [ 50 | '/*eslint no-undef:1*/', 51 | 'var React;', 52 | 'class Hello extends React.Component {', 53 | ' render() {', 54 | ' return ', 55 | ' }', 56 | '}' 57 | ].join('\n'), 58 | parserOptions: parserOptions 59 | }], 60 | invalid: [{ 61 | code: '/*eslint no-undef:1*/ var React; React.render();', 62 | errors: [{ 63 | message: '\'App\' is not defined.' 64 | }], 65 | parserOptions: parserOptions 66 | }, { 67 | code: '/*eslint no-undef:1*/ var React; React.render();', 68 | errors: [{ 69 | message: '\'Appp\' is not defined.' 70 | }], 71 | parserOptions: parserOptions 72 | }, { 73 | code: '/*eslint no-undef:1*/ var React; React.render();', 74 | errors: [{ 75 | message: '\'Apppp\' is not defined.' 76 | }], 77 | parserOptions: parserOptions 78 | }, { 79 | code: '/*eslint no-undef:1*/ var React; React.render();', 80 | errors: [{ 81 | message: '\'appp\' is not defined.' 82 | }], 83 | parserOptions: parserOptions 84 | }, { 85 | code: '/*eslint no-undef:1*/ var React; React.render();', 86 | errors: [{ 87 | message: '\'appp\' is not defined.' 88 | }], 89 | parserOptions: parserOptions 90 | }] 91 | }); 92 | -------------------------------------------------------------------------------- /lib/rules/no-string-refs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent string definitions for references and prevent referencing this.refs 3 | * @author Tom Hastjarjanto 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 | * Checks if we are using refs 16 | * @param {ASTNode} node The AST node being checked. 17 | * @returns {Boolean} True if we are using refs, false if not. 18 | */ 19 | function isRefsUsage(node) { 20 | return Boolean( 21 | ( 22 | utils.getParentES6Component() || 23 | utils.getParentES5Component() 24 | ) && 25 | node.object.type === 'ThisExpression' && 26 | node.property.name === 'refs' 27 | ); 28 | } 29 | 30 | /** 31 | * Checks if we are using a ref attribute 32 | * @param {ASTNode} node The AST node being checked. 33 | * @returns {Boolean} True if we are using a ref attribute, false if not. 34 | */ 35 | function isRefAttribute(node) { 36 | return Boolean( 37 | node.type === 'JSXAttribute' && 38 | node.name && 39 | node.name.name === 'ref' 40 | ); 41 | } 42 | 43 | /** 44 | * Checks if a node contains a string value 45 | * @param {ASTNode} node The AST node being checked. 46 | * @returns {Boolean} True if the node contains a string value, false if not. 47 | */ 48 | function containsStringLiteral(node) { 49 | return Boolean( 50 | node.value && 51 | node.value.type === 'Literal' && 52 | typeof node.value.value === 'string' 53 | ); 54 | } 55 | 56 | /** 57 | * Checks if a node contains a string value within a jsx expression 58 | * @param {ASTNode} node The AST node being checked. 59 | * @returns {Boolean} True if the node contains a string value within a jsx expression, false if not. 60 | */ 61 | function containsStringExpressionContainer(node) { 62 | return Boolean( 63 | node.value && 64 | node.value.type === 'JSXExpressionContainer' && 65 | node.value.expression && 66 | node.value.expression.type === 'Literal' && 67 | typeof node.value.expression.value === 'string' 68 | ); 69 | } 70 | 71 | return { 72 | MemberExpression: function(node) { 73 | if (isRefsUsage(node)) { 74 | context.report({ 75 | node: node, 76 | message: 'Using this.refs is deprecated.' 77 | }); 78 | } 79 | }, 80 | JSXAttribute: function(node) { 81 | if ( 82 | isRefAttribute(node) && 83 | (containsStringLiteral(node) || containsStringExpressionContainer(node)) 84 | ) { 85 | context.report({ 86 | node: node, 87 | message: 'Using string literals in ref attributes is deprecated.' 88 | }); 89 | } 90 | } 91 | }; 92 | }); 93 | 94 | module.exports.schema = []; 95 | -------------------------------------------------------------------------------- /tests/lib/rules/react-in-jsx-scope.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for react-in-jsx-scope 3 | * @author Glen Mailer 4 | */ 5 | 6 | 'use strict'; 7 | 8 | // ----------------------------------------------------------------------------- 9 | // Requirements 10 | // ----------------------------------------------------------------------------- 11 | 12 | var rule = require('../../../lib/rules/react-in-jsx-scope'); 13 | var RuleTester = require('eslint').RuleTester; 14 | 15 | var parserOptions = { 16 | ecmaVersion: 6, 17 | sourceType: 'module', 18 | ecmaFeatures: { 19 | jsx: true 20 | } 21 | }; 22 | 23 | var settings = { 24 | react: { 25 | pragma: 'Foo' 26 | } 27 | }; 28 | 29 | // ----------------------------------------------------------------------------- 30 | // Tests 31 | // ----------------------------------------------------------------------------- 32 | 33 | var ruleTester = new RuleTester(); 34 | ruleTester.run('react-in-jsx-scope', rule, { 35 | valid: [ 36 | {code: 'var React, App; ;', parserOptions: parserOptions}, 37 | {code: 'var React; ;', parserOptions: parserOptions}, 38 | {code: 'var React; ;', parserOptions: parserOptions}, 39 | {code: 'var React, App, a=1; ;', parserOptions: parserOptions}, 40 | {code: 'var React, App, a=1; function elem() { return ; }', parserOptions: parserOptions}, 41 | {code: 'var React, App; ;', parserOptions: parserOptions}, 42 | {code: '/** @jsx Foo */ var Foo, App; ;', parserOptions: parserOptions}, 43 | {code: '/** @jsx Foo.Bar */ var Foo, App; ;', parserOptions: parserOptions}, 44 | {code: [ 45 | 'import React from \'react/addons\';', 46 | 'const Button = React.createClass({', 47 | ' render() {', 48 | ' return (', 49 | ' ', 50 | ' )', 51 | ' }', 52 | '});', 53 | 'export default Button;' 54 | ].join('\n'), 55 | parserOptions: parserOptions}, 56 | {code: 'var Foo, App; ;', settings: settings, parserOptions: parserOptions} 57 | ], 58 | invalid: [ 59 | {code: 'var App, a = ;', 60 | errors: [{message: '\'React\' must be in scope when using JSX'}], parserOptions: parserOptions}, 61 | {code: 'var a = ;', 62 | errors: [{message: '\'React\' must be in scope when using JSX'}], parserOptions: parserOptions}, 63 | {code: 'var a = ;', 64 | errors: [{message: '\'React\' must be in scope when using JSX'}], parserOptions: parserOptions}, 65 | {code: '/** @jsx React.DOM */ var a = ;', 66 | errors: [{message: '\'React\' must be in scope when using JSX'}], parserOptions: parserOptions}, 67 | {code: '/** @jsx Foo.bar */ var React, a = ;', 68 | errors: [{message: '\'Foo\' must be in scope when using JSX'}], parserOptions: parserOptions}, 69 | {code: 'var React, a = ;', 70 | errors: [{message: '\'Foo\' must be in scope when using JSX'}], settings: settings, parserOptions: parserOptions} 71 | ] 72 | }); 73 | -------------------------------------------------------------------------------- /tests/lib/rules/require-extension.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Restrict file extensions that may be required 3 | * @author Scott Andrews 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Requirements 9 | // ------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../lib/rules/require-extension'); 12 | var RuleTester = require('eslint').RuleTester; 13 | 14 | // ------------------------------------------------------------------------------ 15 | // Code Snippets 16 | // ------------------------------------------------------------------------------ 17 | 18 | var REQUIRE_PACKAGE = 'require(\'eslint\')'; 19 | 20 | var REQUIRE_PACKAGE_JS = 'require(\'headroom.js\')'; 21 | 22 | var REQUIRE_JS = 'require(\'./index.js\')'; 23 | 24 | var REQUIRE_JSX = 'require(\'./index.jsx\')'; 25 | 26 | var REQUIRE_JSON = 'require(\'./index.json\')'; 27 | 28 | var REQUIRE_EMPTY = 'require()'; 29 | 30 | var REQUIRE_OBJECT = 'require({})'; 31 | 32 | // ------------------------------------------------------------------------------ 33 | // Tests 34 | // ------------------------------------------------------------------------------ 35 | 36 | var ruleTester = new RuleTester(); 37 | ruleTester.run('require-extension', rule, { 38 | 39 | valid: [ 40 | { 41 | code: REQUIRE_PACKAGE 42 | }, { 43 | code: REQUIRE_JS 44 | }, { 45 | code: REQUIRE_JSON 46 | }, { 47 | code: REQUIRE_EMPTY 48 | }, { 49 | code: REQUIRE_OBJECT 50 | }, { 51 | code: REQUIRE_PACKAGE, 52 | args: [1] 53 | }, { 54 | code: REQUIRE_JS, 55 | args: [1] 56 | }, { 57 | code: REQUIRE_JSON, 58 | args: [1] 59 | }, { 60 | code: REQUIRE_EMPTY, 61 | args: [1] 62 | }, { 63 | code: REQUIRE_OBJECT, 64 | args: [1] 65 | }, { 66 | code: REQUIRE_JSON, 67 | options: [{extensions: ['.js']}] 68 | }, { 69 | code: REQUIRE_JSX, 70 | options: [{extensions: ['.js']}] 71 | }, { 72 | code: REQUIRE_PACKAGE_JS, 73 | options: [{extensions: ['.js']}] 74 | } 75 | ], 76 | 77 | invalid: [ 78 | { 79 | code: REQUIRE_JSX, 80 | errors: [{message: 'Unable to require module with extension \'.jsx\''}] 81 | }, { 82 | code: REQUIRE_JSX, 83 | args: [1], 84 | errors: [{message: 'Unable to require module with extension \'.jsx\''}] 85 | }, { 86 | code: REQUIRE_JS, 87 | options: [{extensions: ['.js']}], 88 | errors: [{message: 'Unable to require module with extension \'.js\''}] 89 | }, { 90 | code: REQUIRE_JS, 91 | options: [{extensions: ['.js', '.jsx']}], 92 | errors: [{message: 'Unable to require module with extension \'.js\''}] 93 | }, { 94 | code: REQUIRE_JSX, 95 | options: [{extensions: ['.js', '.jsx']}], 96 | errors: [{message: 'Unable to require module with extension \'.jsx\''}] 97 | } 98 | ] 99 | 100 | }); 101 | -------------------------------------------------------------------------------- /docs/rules/display-name.md: -------------------------------------------------------------------------------- 1 | # Prevent missing displayName in a React component definition (display-name) 2 | 3 | DisplayName allows you to name your component. This name is used by React in debugging messages. 4 | 5 | ## Rule Details 6 | 7 | The following patterns are considered warnings: 8 | 9 | ```js 10 | var Hello = React.createClass({ 11 | render: function() { 12 | return
Hello {this.props.name}
; 13 | } 14 | }); 15 | ``` 16 | 17 | The following patterns are not considered warnings: 18 | 19 | ```js 20 | var Hello = React.createClass({ 21 | displayName: 'Hello', 22 | render: function() { 23 | return
Hello {this.props.name}
; 24 | } 25 | }); 26 | ``` 27 | 28 | ## Rule Options 29 | 30 | ```js 31 | ... 32 | "display-name": [, { "ignoreTranspilerName": }] 33 | ... 34 | ``` 35 | 36 | ### `ignoreTranspilerName` 37 | 38 | When `true` the rule will ignore the name set by the transpiler and require a `displayName` property in this case. 39 | 40 | The following patterns are considered okay and do not cause warnings: 41 | 42 | ```js 43 | var Hello = React.createClass({ 44 | displayName: 'Hello', 45 | 46 | render: function() { 47 | return
Hello {this.props.name}
; 48 | } 49 | }); 50 | module.exports = Hello; 51 | ``` 52 | 53 | ```js 54 | export default class Hello extends React.Component { 55 | render() { 56 | return
Hello {this.props.name}
; 57 | } 58 | } 59 | Hello.displayName = 'Hello'; 60 | ``` 61 | 62 | ```js 63 | export default function Hello({ name }) { 64 | return
Hello {name}
; 65 | } 66 | Hello.displayName = 'Hello'; 67 | ``` 68 | 69 | The following patterns will cause warnings: 70 | 71 | ```js 72 | var Hello = React.createClass({ 73 | render: function() { 74 | return
Hello {this.props.name}
; 75 | } 76 | }); 77 | module.exports = Hello; 78 | ``` 79 | 80 | ```js 81 | export default class Hello extends React.Component { 82 | render() { 83 | return
Hello {this.props.name}
; 84 | } 85 | } 86 | ``` 87 | 88 | ```js 89 | module.exports = React.createClass({ 90 | render: function() { 91 | return
Hello {this.props.name}
; 92 | } 93 | }); 94 | ``` 95 | 96 | ```js 97 | export default class extends React.Component { 98 | render() { 99 | return
Hello {this.props.name}
; 100 | } 101 | } 102 | ``` 103 | 104 | ```js 105 | function HelloComponent() { 106 | return React.createClass({ 107 | render: function() { 108 | return
Hello {this.props.name}
; 109 | } 110 | }); 111 | } 112 | module.exports = HelloComponent(); 113 | ``` 114 | 115 | ## About component detection 116 | 117 | For this rule to work we need to detect React components, this could be very hard since components could be declared in a lot of ways. 118 | 119 | For now we should detect components created with: 120 | 121 | * `React.createClass()` 122 | * an ES6 class that inherit from `React.Component` or `Component` 123 | * a stateless function that return JSX or the result of a `React.createElement` call. 124 | -------------------------------------------------------------------------------- /tests/lib/rules/jsx-indent-props.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Validate props indentation in JSX 3 | * @author Yannick Croissant 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Requirements 9 | // ------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../lib/rules/jsx-indent-props'); 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('jsx-indent-props', rule, { 27 | valid: [{ 28 | code: [ 29 | '' 31 | ].join('\n'), 32 | parserOptions: parserOptions 33 | }, { 34 | code: [ 35 | '' 38 | ].join('\n'), 39 | options: [2], 40 | parserOptions: parserOptions 41 | }, { 42 | code: [ 43 | '' 46 | ].join('\n'), 47 | options: [0], 48 | parserOptions: parserOptions 49 | }, { 50 | code: [ 51 | ' ' 54 | ].join('\n'), 55 | options: [-2], 56 | parserOptions: parserOptions 57 | }, { 58 | code: [ 59 | '' 62 | ].join('\n'), 63 | options: ['tab'], 64 | parserOptions: parserOptions 65 | }], 66 | 67 | invalid: [{ 68 | code: [ 69 | '' 72 | ].join('\n'), 73 | output: [ 74 | '' 77 | ].join('\n'), 78 | parserOptions: parserOptions, 79 | errors: [{message: 'Expected indentation of 4 space characters but found 2.'}] 80 | }, { 81 | code: [ 82 | '' 85 | ].join('\n'), 86 | output: [ 87 | '' 90 | ].join('\n'), 91 | options: [2], 92 | parserOptions: parserOptions, 93 | errors: [{message: 'Expected indentation of 2 space characters but found 4.'}] 94 | }, { 95 | code: [ 96 | '' 99 | ].join('\n'), 100 | output: [ 101 | '' 104 | ].join('\n'), 105 | options: ['tab'], 106 | parserOptions: parserOptions, 107 | errors: [{message: 'Expected indentation of 1 tab character but found 0.'}] 108 | }, { 109 | code: [ 110 | '' 113 | ].join('\n'), 114 | output: [ 115 | '' 118 | ].join('\n'), 119 | options: ['tab'], 120 | parserOptions: parserOptions, 121 | errors: [{message: 'Expected indentation of 1 tab character but found 3.'}] 122 | }] 123 | }); 124 | -------------------------------------------------------------------------------- /tests/lib/rules/no-is-mounted.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent usage of isMounted 3 | * @author Joe Lencioni 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Requirements 9 | // ------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../lib/rules/no-is-mounted'); 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-is-mounted', rule, { 27 | 28 | valid: [{ 29 | code: [ 30 | 'var Hello = function() {', 31 | '};' 32 | ].join('\n'), 33 | parserOptions: parserOptions 34 | }, { 35 | code: [ 36 | 'var Hello = React.createClass({', 37 | ' render: function() {', 38 | ' return
Hello
;', 39 | ' }', 40 | '});' 41 | ].join('\n'), 42 | parserOptions: parserOptions 43 | }, { 44 | code: [ 45 | 'var Hello = React.createClass({', 46 | ' componentDidUpdate: function() {', 47 | ' someNonMemberFunction(arg);', 48 | ' this.someFunc = this.isMounted;', 49 | ' },', 50 | ' render: function() {', 51 | ' return
Hello
;', 52 | ' }', 53 | '});' 54 | ].join('\n'), 55 | parserOptions: parserOptions 56 | }], 57 | 58 | invalid: [{ 59 | code: [ 60 | 'var Hello = React.createClass({', 61 | ' componentDidUpdate: function() {', 62 | ' if (!this.isMounted()) {', 63 | ' return;', 64 | ' }', 65 | ' },', 66 | ' render: function() {', 67 | ' return
Hello
;', 68 | ' }', 69 | '});' 70 | ].join('\n'), 71 | parserOptions: parserOptions, 72 | errors: [{ 73 | message: 'Do not use isMounted' 74 | }] 75 | }, { 76 | code: [ 77 | 'var Hello = React.createClass({', 78 | ' someMethod: function() {', 79 | ' if (!this.isMounted()) {', 80 | ' return;', 81 | ' }', 82 | ' },', 83 | ' render: function() {', 84 | ' return
Hello
;', 85 | ' }', 86 | '});' 87 | ].join('\n'), 88 | parserOptions: parserOptions, 89 | errors: [{ 90 | message: 'Do not use isMounted' 91 | }] 92 | }, { 93 | code: [ 94 | 'class Hello extends React.Component {', 95 | ' someMethod() {', 96 | ' if (!this.isMounted()) {', 97 | ' return;', 98 | ' }', 99 | ' }', 100 | ' render() {', 101 | ' return
Hello
;', 102 | ' }', 103 | '};' 104 | ].join('\n'), 105 | parserOptions: parserOptions, 106 | errors: [{ 107 | message: 'Do not use isMounted' 108 | }] 109 | }] 110 | }); 111 | -------------------------------------------------------------------------------- /tests/lib/rules/jsx-handler-names.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for jsx-handler-names 3 | * @author Jake Marsh 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Requirements 9 | // ------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../lib/rules/jsx-handler-names'); 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('jsx-handler-names', rule, { 27 | valid: [{ 28 | code: [ 29 | '' 30 | ].join('\n'), 31 | parserOptions: parserOptions 32 | }, { 33 | code: [ 34 | '' 35 | ].join('\n'), 36 | parserOptions: parserOptions 37 | }, { 38 | code: [ 39 | '' 40 | ].join('\n'), 41 | parserOptions: parserOptions 42 | }, { 43 | code: [ 44 | '' 45 | ].join('\n'), 46 | parserOptions: parserOptions 47 | }, { 48 | code: [ 49 | '' 50 | ].join('\n'), 51 | parserOptions: parserOptions 52 | }, { 53 | code: [ 54 | '' 55 | ].join('\n'), 56 | parserOptions: parserOptions 57 | }, { 58 | code: [ 59 | '' 60 | ].join('\n'), 61 | parserOptions: parserOptions 62 | }, { 63 | code: [ 64 | '' 65 | ].join('\n'), 66 | parserOptions: parserOptions 67 | }, { 68 | code: [ 69 | '' 70 | ].join('\n'), 71 | parserOptions: parserOptions 72 | }, { 73 | code: [ 74 | '' 75 | ].join('\n'), 76 | options: [{ 77 | eventHandlerPrefix: 'on', 78 | eventHandlerPropPrefix: 'on' 79 | }], 80 | parserOptions: parserOptions 81 | }], 82 | 83 | invalid: [{ 84 | code: [ 85 | '' 86 | ].join('\n'), 87 | parserOptions: parserOptions, 88 | errors: [{message: 'Handler function for onChange prop key must begin with \'handle\''}] 89 | }, { 90 | code: [ 91 | '' 92 | ].join('\n'), 93 | parserOptions: parserOptions, 94 | errors: [{message: 'Prop key for handleChange must begin with \'on\''}] 95 | }, { 96 | code: [ 97 | '' 98 | ].join('\n'), 99 | parserOptions: parserOptions, 100 | errors: [{message: 'Handler function for onChange prop key must begin with \'handle\''}] 101 | }] 102 | }); 103 | -------------------------------------------------------------------------------- /docs/rules/jsx-curly-spacing.md: -------------------------------------------------------------------------------- 1 | # Enforce or disallow spaces inside of curly braces in JSX attributes. (jsx-curly-spacing) 2 | 3 | While formatting preferences are very personal, a number of style guides require or disallow spaces between curly braces. 4 | 5 | **Fixable:** This rule is automatically fixable using the `--fix` flag on the command line. 6 | 7 | ## Rule Details 8 | 9 | This rule aims to maintain consistency around the spacing inside of JSX attributes. 10 | 11 | It either requires or disallows spaces between those braces and the values inside of them. 12 | 13 | ### Options 14 | 15 | There are two main options for the rule: 16 | 17 | * `"always"` enforces a space inside of curly braces 18 | * `"never"` disallows spaces inside of curly braces (default) 19 | 20 | Depending on your coding conventions, you can choose either option by specifying it in your configuration: 21 | 22 | ```json 23 | "jsx-curly-spacing": [2, "always"] 24 | ``` 25 | 26 | #### never 27 | 28 | When `"never"` is set, the following patterns are considered warnings: 29 | 30 | ```js 31 | ; 32 | ; 33 | ; 34 | ``` 35 | 36 | The following patterns are not warnings: 37 | 38 | ```js 39 | ; 40 | ; 41 | ; 44 | ``` 45 | 46 | #### always 47 | 48 | When `"always"` is used, the following patterns are considered warnings: 49 | 50 | ```js 51 | ; 52 | ; 53 | ; 54 | ``` 55 | 56 | The following patterns are not warnings: 57 | 58 | ```js 59 | ; 60 | ; 61 | ; 64 | ``` 65 | 66 | #### Braces spanning multiple lines 67 | 68 | By default, braces spanning multiple lines are allowed with either setting. If you want to disallow them you can specify an additional `allowMultiline` property with the value `false`: 69 | 70 | ```json 71 | "jsx-curly-spacing": [2, "never", {"allowMultiline": false}] 72 | ``` 73 | 74 | When `"never"` is used and `allowMultiline` is `false`, the following patterns are considered warnings: 75 | 76 | ```js 77 | ; 78 | ; 79 | ; 80 | ; 83 | ``` 84 | 85 | The following patterns are not warnings: 86 | 87 | ```js 88 | ; 89 | ; 90 | ``` 91 | 92 | When `"always"` is used and `allowMultiline` is `false`, the following patterns are considered warnings: 93 | 94 | ```js 95 | ; 96 | ; 97 | ; 98 | ; 101 | ``` 102 | 103 | The following patterns are not warnings: 104 | 105 | ```js 106 | ; 107 | ; 108 | ``` 109 | 110 | 111 | ## When Not To Use It 112 | 113 | You can turn this rule off if you are not concerned with the consistency around the spacing inside of JSX attributes. 114 | -------------------------------------------------------------------------------- /lib/rules/jsx-equals-spacing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Disallow or enforce spaces around equal signs in JSX attributes. 3 | * @author ryym 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Rule Definition 9 | // ------------------------------------------------------------------------------ 10 | 11 | module.exports = function(context) { 12 | var config = context.options[0]; 13 | var sourceCode = context.getSourceCode(); 14 | 15 | /** 16 | * Determines a given attribute node has an equal sign. 17 | * @param {ASTNode} attrNode - The attribute node. 18 | * @returns {boolean} Whether or not the attriute node has an equal sign. 19 | */ 20 | function hasEqual(attrNode) { 21 | return attrNode.type !== 'JSXSpreadAttribute' && attrNode.value !== null; 22 | } 23 | 24 | // -------------------------------------------------------------------------- 25 | // Public 26 | // -------------------------------------------------------------------------- 27 | 28 | return { 29 | JSXOpeningElement: function(node) { 30 | node.attributes.forEach(function(attrNode) { 31 | if (!hasEqual(attrNode)) { 32 | return; 33 | } 34 | 35 | var equalToken = sourceCode.getTokenAfter(attrNode.name); 36 | var spacedBefore = sourceCode.isSpaceBetweenTokens(attrNode.name, equalToken); 37 | var spacedAfter = sourceCode.isSpaceBetweenTokens(equalToken, attrNode.value); 38 | 39 | switch (config) { 40 | default: 41 | case 'never': 42 | if (spacedBefore) { 43 | context.report({ 44 | node: attrNode, 45 | loc: equalToken.loc.start, 46 | message: 'There should be no space before \'=\'', 47 | fix: function(fixer) { 48 | return fixer.removeRange([attrNode.name.range[1], equalToken.start]); 49 | } 50 | }); 51 | } 52 | if (spacedAfter) { 53 | context.report({ 54 | node: attrNode, 55 | loc: equalToken.loc.start, 56 | message: 'There should be no space after \'=\'', 57 | fix: function(fixer) { 58 | return fixer.removeRange([equalToken.end, attrNode.value.range[0]]); 59 | } 60 | }); 61 | } 62 | break; 63 | case 'always': 64 | if (!spacedBefore) { 65 | context.report({ 66 | node: attrNode, 67 | loc: equalToken.loc.start, 68 | message: 'A space is required before \'=\'', 69 | fix: function(fixer) { 70 | return fixer.insertTextBefore(equalToken, ' '); 71 | } 72 | }); 73 | } 74 | if (!spacedAfter) { 75 | context.report({ 76 | node: attrNode, 77 | loc: equalToken.loc.start, 78 | message: 'A space is required after \'=\'', 79 | fix: function(fixer) { 80 | return fixer.insertTextAfter(equalToken, ' '); 81 | } 82 | }); 83 | } 84 | break; 85 | } 86 | }); 87 | } 88 | }; 89 | }; 90 | 91 | module.exports.schema = [{ 92 | enum: ['always', 'never'] 93 | }]; 94 | -------------------------------------------------------------------------------- /lib/rules/jsx-sort-props.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Enforce props alphabetical sorting 3 | * @author Ilya Volodin, Yannick Croissant 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Rule Definition 9 | // ------------------------------------------------------------------------------ 10 | 11 | function isCallbackPropName(propName) { 12 | return /^on[A-Z]/.test(propName); 13 | } 14 | 15 | module.exports = function(context) { 16 | 17 | var configuration = context.options[0] || {}; 18 | var ignoreCase = configuration.ignoreCase || false; 19 | var callbacksLast = configuration.callbacksLast || false; 20 | var shorthandFirst = configuration.shorthandFirst || false; 21 | 22 | return { 23 | JSXOpeningElement: function(node) { 24 | node.attributes.reduce(function(memo, decl, idx, attrs) { 25 | if (decl.type === 'JSXSpreadAttribute') { 26 | return attrs[idx + 1]; 27 | } 28 | 29 | var previousPropName = memo.name.name; 30 | var currentPropName = decl.name.name; 31 | var previousValue = memo.value; 32 | var currentValue = decl.value; 33 | var previousIsCallback = isCallbackPropName(previousPropName); 34 | var currentIsCallback = isCallbackPropName(currentPropName); 35 | 36 | if (ignoreCase) { 37 | previousPropName = previousPropName.toLowerCase(); 38 | currentPropName = currentPropName.toLowerCase(); 39 | } 40 | 41 | if (callbacksLast) { 42 | if (!previousIsCallback && currentIsCallback) { 43 | // Entering the callback prop section 44 | return decl; 45 | } 46 | if (previousIsCallback && !currentIsCallback) { 47 | // Encountered a non-callback prop after a callback prop 48 | context.report({ 49 | node: memo, 50 | message: 'Callbacks must be listed after all other props' 51 | }); 52 | return memo; 53 | } 54 | } 55 | 56 | if (shorthandFirst) { 57 | if (currentValue && !previousValue) { 58 | return decl; 59 | } 60 | if (!currentValue && previousValue) { 61 | context.report({ 62 | node: memo, 63 | message: 'Shorthand props must be listed before all other props' 64 | }); 65 | return memo; 66 | } 67 | } 68 | 69 | if (currentPropName < previousPropName) { 70 | context.report({ 71 | node: decl, 72 | message: 'Props should be sorted alphabetically' 73 | }); 74 | return memo; 75 | } 76 | 77 | return decl; 78 | }, node.attributes[0]); 79 | } 80 | }; 81 | }; 82 | 83 | module.exports.schema = [{ 84 | type: 'object', 85 | properties: { 86 | // Whether callbacks (prefixed with "on") should be listed at the very end, 87 | // after all other props. 88 | callbacksLast: { 89 | type: 'boolean' 90 | }, 91 | // Whether shorthand properties (without a value) should be listed first 92 | shorthandFirst: { 93 | type: 'boolean' 94 | }, 95 | ignoreCase: { 96 | type: 'boolean' 97 | } 98 | }, 99 | additionalProperties: false 100 | }]; 101 | -------------------------------------------------------------------------------- /tests/lib/rules/no-set-state.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent usage of setState 3 | * @author Mark Dalgleish 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Requirements 9 | // ------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../lib/rules/no-set-state'); 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-set-state', rule, { 27 | 28 | valid: [{ 29 | code: [ 30 | 'var Hello = function() {', 31 | ' this.setState({})', 32 | '};' 33 | ].join('\n'), 34 | parserOptions: parserOptions 35 | }, { 36 | code: [ 37 | 'var Hello = React.createClass({', 38 | ' render: function() {', 39 | ' return
Hello {this.props.name}
;', 40 | ' }', 41 | '});' 42 | ].join('\n'), 43 | parserOptions: parserOptions 44 | }, { 45 | code: [ 46 | 'var Hello = React.createClass({', 47 | ' componentDidUpdate: function() {', 48 | ' someNonMemberFunction(arg);', 49 | ' this.someHandler = this.setState;', 50 | ' },', 51 | ' render: function() {', 52 | ' return
Hello {this.props.name}
;', 53 | ' }', 54 | '});' 55 | ].join('\n'), 56 | parserOptions: parserOptions 57 | }], 58 | 59 | invalid: [{ 60 | code: [ 61 | 'var Hello = React.createClass({', 62 | ' componentDidUpdate: function() {', 63 | ' this.setState({', 64 | ' name: this.props.name.toUpperCase()', 65 | ' });', 66 | ' },', 67 | ' render: function() {', 68 | ' return
Hello {this.state.name}
;', 69 | ' }', 70 | '});' 71 | ].join('\n'), 72 | parserOptions: parserOptions, 73 | errors: [{ 74 | message: 'Do not use setState' 75 | }] 76 | }, { 77 | code: [ 78 | 'var Hello = React.createClass({', 79 | ' someMethod: function() {', 80 | ' this.setState({', 81 | ' name: this.props.name.toUpperCase()', 82 | ' });', 83 | ' },', 84 | ' render: function() {', 85 | ' return
Hello {this.state.name}
;', 86 | ' }', 87 | '});' 88 | ].join('\n'), 89 | parserOptions: parserOptions, 90 | errors: [{ 91 | message: 'Do not use setState' 92 | }] 93 | }, { 94 | code: [ 95 | 'class Hello extends React.Component {', 96 | ' someMethod() {', 97 | ' this.setState({', 98 | ' name: this.props.name.toUpperCase()', 99 | ' });', 100 | ' }', 101 | ' render() {', 102 | ' return
Hello {this.state.name}
;', 103 | ' }', 104 | '};' 105 | ].join('\n'), 106 | parserOptions: parserOptions, 107 | errors: [{ 108 | message: 'Do not use setState' 109 | }] 110 | }] 111 | }); 112 | -------------------------------------------------------------------------------- /docs/rules/sort-prop-types.md: -------------------------------------------------------------------------------- 1 | # Enforce propTypes declarations alphabetical sorting (sort-prop-types) 2 | 3 | Some developers prefer to sort propTypes declarations alphabetically to be able to find necessary declaration easier at the later time. Others feel that it adds complexity and becomes burden to maintain. 4 | 5 | ## Rule Details 6 | 7 | This rule checks all components and verifies that all propTypes declarations are sorted alphabetically. A spread attribute resets the verification. The default configuration of the rule is case-sensitive. 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```js 12 | var Component = React.createClass({ 13 | propTypes: { 14 | z: React.PropTypes.number, 15 | a: React.PropTypes.any, 16 | b: React.PropTypes.string 17 | }, 18 | ... 19 | }); 20 | 21 | class Component extends React.Component { 22 | ... 23 | } 24 | Component.propTypes = { 25 | z: React.PropTypes.number, 26 | a: React.PropTypes.any, 27 | b: React.PropTypes.string 28 | }; 29 | 30 | class Component extends React.Component { 31 | static propTypes = { 32 | z: React.PropTypes.any, 33 | y: React.PropTypes.any, 34 | a: React.PropTypes.any 35 | } 36 | render() { 37 | return
; 38 | } 39 | } 40 | ``` 41 | 42 | The following patterns are considered okay and do not cause warnings: 43 | 44 | ```js 45 | var Component = React.createClass({ 46 | propTypes: { 47 | a: React.PropTypes.number, 48 | b: React.PropTypes.any, 49 | c: React.PropTypes.string 50 | }, 51 | ... 52 | }); 53 | 54 | class Component extends React.Component { 55 | ... 56 | } 57 | Component.propTypes = { 58 | a: React.PropTypes.string, 59 | b: React.PropTypes.any, 60 | c: React.PropTypes.string 61 | }; 62 | 63 | class Component extends React.Component { 64 | static propTypes = { 65 | a: React.PropTypes.any, 66 | b: React.PropTypes.any, 67 | c: React.PropTypes.any 68 | } 69 | render() { 70 | return
; 71 | } 72 | } 73 | ``` 74 | 75 | ## Rule Options 76 | 77 | ```js 78 | ... 79 | "sort-prop-types": [, { 80 | "callbacksLast": , 81 | "ignoreCase": , 82 | "requiredFirst": , 83 | }] 84 | ... 85 | ``` 86 | 87 | ### `ignoreCase` 88 | 89 | When `true` the rule ignores the case-sensitivity of the declarations order. 90 | 91 | ### `callbacksLast` 92 | 93 | When `true`, propTypes for props beginning with "on" must be listed after all other props: 94 | 95 | ```js 96 | var Component = React.createClass({ 97 | propTypes: { 98 | a: React.PropTypes.number, 99 | z: React.PropTypes.string, 100 | onBar: React.PropTypes.func, 101 | onFoo: React.PropTypes.func, 102 | }, 103 | ... 104 | }); 105 | ``` 106 | 107 | ### `requiredFirst` 108 | 109 | When `true`, prop types for required props must be listed before all other props: 110 | 111 | ```js 112 | var Component = React.createClass({ 113 | propTypes: { 114 | barRequired: React.PropTypes.any.isRequired, 115 | fooRequired: React.PropTypes.any.isRequired, 116 | a: React.PropTypes.number, 117 | z: React.PropTypes.string, 118 | }, 119 | ... 120 | }); 121 | ``` 122 | 123 | ## When not to use 124 | 125 | This rule is a formatting preference and not following it won't negatively affect the quality of your code. If alphabetizing props declarations isn't a part of your coding standards, then you can leave this rule off. 126 | -------------------------------------------------------------------------------- /docs/rules/prop-types.md: -------------------------------------------------------------------------------- 1 | # Prevent missing props validation in a React component definition (prop-types) 2 | 3 | PropTypes improve the reusability of your component by validating the received data. 4 | 5 | It can warn other developers if they make a mistake while reusing the component with improper data type. 6 | 7 | ## Rule Details 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```jsx 12 | var Hello = React.createClass({ 13 | render: function() { 14 | return
Hello {this.props.name}
; 15 | } 16 | }); 17 | 18 | var Hello = React.createClass({ 19 | propTypes: { 20 | firstname: React.PropTypes.string.isRequired 21 | }, 22 | render: function() { 23 | return
Hello {this.props.firstname} {this.props.lastname}
; // lastname type is not defined in propTypes 24 | } 25 | }); 26 | 27 | function Hello({ name }) { 28 | return
Hello {name}
; 29 | } 30 | ``` 31 | 32 | The following patterns are not considered warnings: 33 | 34 | ```jsx 35 | var Hello = React.createClass({ 36 | render: function() { 37 | return
Hello World
; 38 | } 39 | }); 40 | 41 | var Hello = React.createClass({ 42 | propTypes: { 43 | name: React.PropTypes.string.isRequired 44 | }, 45 | render: function() { 46 | return
Hello {this.props.name}
; 47 | } 48 | }); 49 | 50 | // Referencing an external object disable the rule for the component 51 | var Hello = React.createClass({ 52 | propTypes: myPropTypes, 53 | render: function() { 54 | return
Hello {this.props.name}
; 55 | } 56 | }); 57 | 58 | function Hello({ name }) { 59 | return
Hello {name}
; 60 | } 61 | Hello.propTypes = { 62 | name: React.PropTypes.string.isRequired, 63 | }; 64 | ``` 65 | 66 | ## Rule Options 67 | 68 | This rule can take one argument to ignore some specific props during validation. 69 | 70 | ``` 71 | ... 72 | "prop-types": [, { ignore: , customValidators: }] 73 | ... 74 | ``` 75 | 76 | * `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0. 77 | * `ignore`: optional array of props name to ignore during validation. 78 | * `customValidators`: optional array of validators used for propTypes validation. 79 | 80 | ### As for "exceptions" 81 | 82 | It would seem that some common properties such as `props.children` or `props.className` 83 | (and alike) need to be treated as exceptions. 84 | 85 | As it aptly noticed in 86 | [#7](https://github.com/yannickcr/eslint-plugin-react/issues/7) 87 | 88 | > Why should children be an exception? 89 | > Most components don't need `this.props.children`, so that makes it extra important 90 | to document `children` in the propTypes. 91 | 92 | Generally, you should use `React.PropTypes.node` for `children`. It accepts 93 | anything that can be rendered: numbers, strings, elements or an array containing 94 | these types. 95 | 96 | Since 2.0.0 children is no longer ignored for props validation. 97 | 98 | ## About component detection 99 | 100 | For this rule to work we need to detect React components, this could be very hard since components could be declared in a lot of ways. 101 | 102 | For now we should detect components created with: 103 | 104 | * `React.createClass()` 105 | * an ES6 class that inherit from `React.Component` or `Component` 106 | * a stateless function that return JSX or the result of a `React.createElement` call. 107 | -------------------------------------------------------------------------------- /tests/lib/rules/no-string-refs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent string definitions for references and prevent referencing this.refs 3 | * @author Tom Hastjarjanto 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Requirements 9 | // ------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../lib/rules/no-string-refs'); 12 | var RuleTester = require('eslint').RuleTester; 13 | 14 | require('babel-eslint'); 15 | 16 | // ------------------------------------------------------------------------------ 17 | // Tests 18 | // ------------------------------------------------------------------------------ 19 | 20 | var ruleTester = new RuleTester(); 21 | ruleTester.run('no-refs', rule, { 22 | 23 | valid: [{ 24 | code: [ 25 | 'var Hello = React.createClass({', 26 | ' componentDidMount: function() {', 27 | ' var component = this.hello;', 28 | ' },', 29 | ' render: function() {', 30 | ' return
this.hello = c}>Hello {this.props.name}
;', 31 | ' }', 32 | '});' 33 | ].join('\n'), 34 | parser: 'babel-eslint', 35 | ecmaFeatures: { 36 | jsx: true 37 | } 38 | } 39 | ], 40 | 41 | invalid: [{ 42 | code: [ 43 | 'var Hello = React.createClass({', 44 | ' componentDidMount: function() {', 45 | ' var component = this.refs.hello;', 46 | ' },', 47 | ' render: function() {', 48 | ' return
Hello {this.props.name}
;', 49 | ' }', 50 | '});' 51 | ].join('\n'), 52 | parser: 'babel-eslint', 53 | ecmaFeatures: { 54 | classes: true, 55 | jsx: true 56 | }, 57 | errors: [{ 58 | message: 'Using this.refs is deprecated.' 59 | }] 60 | }, { 61 | code: [ 62 | 'var Hello = React.createClass({', 63 | ' render: function() {', 64 | ' return
Hello {this.props.name}
;', 65 | ' }', 66 | '});' 67 | ].join('\n'), 68 | parser: 'babel-eslint', 69 | ecmaFeatures: { 70 | classes: true, 71 | jsx: true 72 | }, 73 | errors: [{ 74 | message: 'Using string literals in ref attributes is deprecated.' 75 | }] 76 | }, { 77 | code: [ 78 | 'var Hello = React.createClass({', 79 | ' render: function() {', 80 | ' return
Hello {this.props.name}
;', 81 | ' }', 82 | '});' 83 | ].join('\n'), 84 | parser: 'babel-eslint', 85 | ecmaFeatures: { 86 | classes: true, 87 | jsx: true 88 | }, 89 | errors: [{ 90 | message: 'Using string literals in ref attributes is deprecated.' 91 | }] 92 | }, { 93 | code: [ 94 | 'var Hello = React.createClass({', 95 | ' componentDidMount: function() {', 96 | ' var component = this.refs.hello;', 97 | ' },', 98 | ' render: function() {', 99 | ' return
Hello {this.props.name}
;', 100 | ' }', 101 | '});' 102 | ].join('\n'), 103 | parser: 'babel-eslint', 104 | ecmaFeatures: { 105 | classes: true, 106 | jsx: true 107 | }, 108 | errors: [{ 109 | message: 'Using this.refs is deprecated.' 110 | }, { 111 | message: 'Using string literals in ref attributes is deprecated.' 112 | }] 113 | } 114 | ]}); 115 | -------------------------------------------------------------------------------- /tests/lib/rules/prefer-es6-class.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prefer es6 class instead of createClass for React Component 3 | * @author Dan Hamilton 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Requirements 9 | // ------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../lib/rules/prefer-es6-class'); 12 | var RuleTester = require('eslint').RuleTester; 13 | 14 | var parserOptions = { 15 | ecmaVersion: 6, 16 | sourceType: 'module', 17 | ecmaFeatures: { 18 | jsx: true 19 | } 20 | }; 21 | 22 | require('babel-eslint'); 23 | 24 | // ------------------------------------------------------------------------------ 25 | // Tests 26 | // ------------------------------------------------------------------------------ 27 | 28 | var ruleTester = new RuleTester(); 29 | ruleTester.run('prefer-es6-class', rule, { 30 | 31 | valid: [{ 32 | code: [ 33 | 'class Hello extends React.Component {', 34 | ' render() {', 35 | ' return
Hello {this.props.name}
;', 36 | ' }', 37 | '}', 38 | 'Hello.displayName = \'Hello\'' 39 | ].join('\n'), 40 | parserOptions: parserOptions 41 | }, { 42 | code: [ 43 | 'export default class Hello extends React.Component {', 44 | ' render() {', 45 | ' return
Hello {this.props.name}
;', 46 | ' }', 47 | '}', 48 | 'Hello.displayName = \'Hello\'' 49 | ].join('\n'), 50 | parserOptions: parserOptions 51 | }, { 52 | code: [ 53 | 'var Hello = "foo";', 54 | 'module.exports = {};' 55 | ].join('\n'), 56 | parserOptions: parserOptions 57 | }, { 58 | code: [ 59 | 'var Hello = React.createClass({', 60 | ' render: function() {', 61 | ' return
Hello {this.props.name}
;', 62 | ' }', 63 | '});' 64 | ].join('\n'), 65 | options: ['never'], 66 | parserOptions: parserOptions 67 | }, { 68 | code: [ 69 | 'class Hello extends React.Component {', 70 | ' render() {', 71 | ' return
Hello {this.props.name}
;', 72 | ' }', 73 | '}' 74 | ].join('\n'), 75 | options: ['always'], 76 | parserOptions: parserOptions 77 | }], 78 | 79 | invalid: [{ 80 | code: [ 81 | 'var Hello = React.createClass({', 82 | ' displayName: \'Hello\',', 83 | ' render: function() {', 84 | ' return
Hello {this.props.name}
;', 85 | ' }', 86 | '});' 87 | ].join('\n'), 88 | parserOptions: parserOptions, 89 | errors: [{ 90 | message: 'Component should use es6 class instead of createClass' 91 | }] 92 | }, { 93 | code: [ 94 | 'var Hello = React.createClass({', 95 | ' render: function() {', 96 | ' return
Hello {this.props.name}
;', 97 | ' }', 98 | '});' 99 | ].join('\n'), 100 | options: ['always'], 101 | parserOptions: parserOptions, 102 | errors: [{ 103 | message: 'Component should use es6 class instead of createClass' 104 | }] 105 | }, { 106 | code: [ 107 | 'class Hello extends React.Component {', 108 | ' render() {', 109 | ' return
Hello {this.props.name}
;', 110 | ' }', 111 | '}' 112 | ].join('\n'), 113 | options: ['never'], 114 | parserOptions: parserOptions, 115 | errors: [{ 116 | message: 'Component should use createClass instead of es6 class' 117 | }] 118 | } 119 | ]}); 120 | -------------------------------------------------------------------------------- /tests/lib/rules/jsx-no-bind.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevents usage of Function.prototype.bind and arrow functions 3 | * in React component definition. 4 | * @author Daniel Lo Nigro 5 | */ 6 | 'use strict'; 7 | 8 | // ----------------------------------------------------------------------------- 9 | // Requirements 10 | // ----------------------------------------------------------------------------- 11 | 12 | var rule = require('../../../lib/rules/jsx-no-bind'); 13 | var RuleTester = require('eslint').RuleTester; 14 | 15 | // ----------------------------------------------------------------------------- 16 | // Tests 17 | // ----------------------------------------------------------------------------- 18 | 19 | var ruleTester = new RuleTester(); 20 | ruleTester.run('jsx-no-bind', rule, { 21 | 22 | valid: [ 23 | // Not covered by the rule 24 | { 25 | code: '
', 26 | parser: 'babel-eslint' 27 | }, 28 | { 29 | code: '
', 30 | parser: 'babel-eslint' 31 | }, 32 | { 33 | code: '
', 34 | parser: 'babel-eslint' 35 | }, 36 | 37 | // bind() and arrow functions in refs explicitly ignored 38 | { 39 | code: '
this._input = c}>
', 40 | options: [{ignoreRefs: true}], 41 | parser: 'babel-eslint' 42 | }, 43 | { 44 | code: '
', 45 | options: [{ignoreRefs: true}], 46 | parser: 'babel-eslint' 47 | }, 48 | 49 | // bind() explicitly allowed 50 | { 51 | code: '
', 52 | options: [{allowBind: true}], 53 | parser: 'babel-eslint' 54 | }, 55 | 56 | // Arrow functions explicitly allowed 57 | { 58 | code: '
alert("1337")}>
', 59 | options: [{allowArrowFunctions: true}], 60 | parser: 'babel-eslint' 61 | } 62 | ], 63 | 64 | invalid: [ 65 | // .bind() 66 | { 67 | code: '
', 68 | errors: [{message: 'JSX props should not use .bind()'}], 69 | parser: 'babel-eslint' 70 | }, 71 | { 72 | code: '
', 73 | errors: [{message: 'JSX props should not use .bind()'}], 74 | parser: 'babel-eslint' 75 | }, 76 | { 77 | code: '
', 78 | errors: [{message: 'JSX props should not use .bind()'}], 79 | parser: 'babel-eslint' 80 | }, 81 | { 82 | code: '
', 83 | errors: [{message: 'JSX props should not use .bind()'}], 84 | parser: 'babel-eslint' 85 | }, 86 | 87 | // Arrow functions 88 | { 89 | code: '
alert("1337")}>
', 90 | errors: [{message: 'JSX props should not use arrow functions'}], 91 | parser: 'babel-eslint' 92 | }, 93 | { 94 | code: '
42}>
', 95 | errors: [{message: 'JSX props should not use arrow functions'}], 96 | parser: 'babel-eslint' 97 | }, 98 | { 99 | code: '
{ first(); second(); }}>
', 100 | errors: [{message: 'JSX props should not use arrow functions'}], 101 | parser: 'babel-eslint' 102 | }, 103 | { 104 | code: '
this._input = c}>
', 105 | errors: [{message: 'JSX props should not use arrow functions'}], 106 | parser: 'babel-eslint' 107 | } 108 | ] 109 | }); 110 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | rules: { 5 | 'jsx-uses-react': require('./lib/rules/jsx-uses-react'), 6 | 'no-multi-comp': require('./lib/rules/no-multi-comp'), 7 | 'prop-types': require('./lib/rules/prop-types'), 8 | 'display-name': require('./lib/rules/display-name'), 9 | 'wrap-multilines': require('./lib/rules/wrap-multilines'), 10 | 'self-closing-comp': require('./lib/rules/self-closing-comp'), 11 | 'no-danger': require('./lib/rules/no-danger'), 12 | 'no-set-state': require('./lib/rules/no-set-state'), 13 | 'no-is-mounted': require('./lib/rules/no-is-mounted'), 14 | 'no-deprecated': require('./lib/rules/no-deprecated'), 15 | 'no-did-mount-set-state': require('./lib/rules/no-did-mount-set-state'), 16 | 'no-did-update-set-state': require('./lib/rules/no-did-update-set-state'), 17 | 'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'), 18 | 'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'), 19 | 'jsx-handler-names': require('./lib/rules/jsx-handler-names'), 20 | 'jsx-pascal-case': require('./lib/rules/jsx-pascal-case'), 21 | 'jsx-no-bind': require('./lib/rules/jsx-no-bind'), 22 | 'jsx-no-undef': require('./lib/rules/jsx-no-undef'), 23 | 'no-unknown-property': require('./lib/rules/no-unknown-property'), 24 | 'jsx-curly-spacing': require('./lib/rules/jsx-curly-spacing'), 25 | 'jsx-equals-spacing': require('./lib/rules/jsx-equals-spacing'), 26 | 'jsx-sort-props': require('./lib/rules/jsx-sort-props'), 27 | 'sort-prop-types': require('./lib/rules/sort-prop-types'), 28 | 'jsx-sort-prop-types': require('./lib/rules/jsx-sort-prop-types'), 29 | 'jsx-boolean-value': require('./lib/rules/jsx-boolean-value'), 30 | 'sort-comp': require('./lib/rules/sort-comp'), 31 | 'require-extension': require('./lib/rules/require-extension'), 32 | 'jsx-no-duplicate-props': require('./lib/rules/jsx-no-duplicate-props'), 33 | 'jsx-max-props-per-line': require('./lib/rules/jsx-max-props-per-line'), 34 | 'jsx-no-literals': require('./lib/rules/jsx-no-literals'), 35 | 'jsx-indent-props': require('./lib/rules/jsx-indent-props'), 36 | 'jsx-indent': require('./lib/rules/jsx-indent'), 37 | 'jsx-closing-bracket-location': require('./lib/rules/jsx-closing-bracket-location'), 38 | 'jsx-space-before-closing': require('./lib/rules/jsx-space-before-closing'), 39 | 'no-direct-mutation-state': require('./lib/rules/no-direct-mutation-state'), 40 | 'forbid-prop-types': require('./lib/rules/forbid-prop-types'), 41 | 'prefer-es6-class': require('./lib/rules/prefer-es6-class'), 42 | 'jsx-key': require('./lib/rules/jsx-key'), 43 | 'no-string-refs': require('./lib/rules/no-string-refs'), 44 | 'prefer-stateless-function': require('./lib/rules/prefer-stateless-function'), 45 | 'require-render-return': require('./lib/rules/require-render-return'), 46 | 'jsx-first-prop-new-line': require('./lib/rules/jsx-first-prop-new-line'), 47 | 'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank') 48 | }, 49 | configs: { 50 | recommended: { 51 | parserOptions: { 52 | ecmaFeatures: { 53 | jsx: true 54 | } 55 | }, 56 | rules: { 57 | 'react/display-name': 2, 58 | 'react/jsx-no-duplicate-props': 2, 59 | 'react/jsx-no-undef': 2, 60 | 'react/jsx-uses-react': 2, 61 | 'react/jsx-uses-vars': 2, 62 | 'react/no-danger': 2, 63 | 'react/no-deprecated': 2, 64 | 'react/no-did-mount-set-state': [2, 'allow-in-func'], 65 | 'react/no-did-update-set-state': [2, 'allow-in-func'], 66 | 'react/no-direct-mutation-state': 2, 67 | 'react/no-is-mounted': 2, 68 | 'react/no-unknown-property': 2, 69 | 'react/prop-types': 2, 70 | 'react/react-in-jsx-scope': 2 71 | } 72 | } 73 | } 74 | }; 75 | -------------------------------------------------------------------------------- /tests/lib/rules/no-deprecated.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent usage of deprecated methods 3 | * @author Yannick Croissant 4 | * @author Scott Feeney 5 | */ 6 | 'use strict'; 7 | 8 | // ------------------------------------------------------------------------------ 9 | // Requirements 10 | // ------------------------------------------------------------------------------ 11 | 12 | var rule = require('../../../lib/rules/no-deprecated'); 13 | var RuleTester = require('eslint').RuleTester; 14 | 15 | var settings = { 16 | react: { 17 | pragma: 'Foo' 18 | } 19 | }; 20 | 21 | require('babel-eslint'); 22 | 23 | // ------------------------------------------------------------------------------ 24 | // Tests 25 | // ------------------------------------------------------------------------------ 26 | 27 | var ruleTester = new RuleTester(); 28 | ruleTester.run('no-deprecated', rule, { 29 | 30 | valid: [ 31 | // Not deprecated 32 | 'var MyClass = React.createClass({});', 33 | 'var element = React.createElement(\'p\', {}, null);', 34 | 'var clone = React.cloneElement(element);', 35 | 'ReactDOM.render(element, container);', 36 | 'ReactDOM.unmountComponentAtNode(container);', 37 | 'ReactDOM.findDOMNode(instance);', 38 | 'ReactDOMServer.renderToString(element);', 39 | 'ReactDOMServer.renderToStaticMarkup(element);', 40 | // Deprecated in a later version 41 | {code: 'React.renderComponent()', options: [{react: '0.11.0'}]}, 42 | {code: 'React.renderComponent()', settings: {react: {version: '0.11.0'}}} 43 | ], 44 | 45 | invalid: [{ 46 | code: 'React.renderComponent()', 47 | options: [{react: '0.12.0'}], 48 | errors: [{ 49 | message: 'React.renderComponent is deprecated since React 0.12.0, use React.render instead' 50 | }] 51 | }, { 52 | code: 'Foo.renderComponent()', 53 | options: [{react: '0.12.0'}], 54 | settings: settings, 55 | errors: [{ 56 | message: 'Foo.renderComponent is deprecated since React 0.12.0, use Foo.render instead' 57 | }] 58 | }, { 59 | code: '/** @jsx Foo */ Foo.renderComponent()', 60 | options: [{react: '0.12.0'}], 61 | errors: [{ 62 | message: 'Foo.renderComponent is deprecated since React 0.12.0, use Foo.render instead' 63 | }] 64 | }, { 65 | code: 'this.transferPropsTo()', 66 | errors: [{ 67 | message: 'this.transferPropsTo is deprecated since React 0.12.0, use spread operator ({...}) instead' 68 | }] 69 | }, { 70 | code: 'React.addons.classSet()', 71 | errors: [{ 72 | message: 'React.addons.classSet is deprecated since React 0.13.0, use the npm module classnames instead' 73 | }] 74 | }, { 75 | code: 'React.render(element, container);', 76 | errors: [{ 77 | message: 'React.render is deprecated since React 0.14.0, use ReactDOM.render instead' 78 | }] 79 | }, { 80 | code: 'React.unmountComponentAtNode(container);', 81 | errors: [{ 82 | message: ( 83 | 'React.unmountComponentAtNode is deprecated since React 0.14.0, ' + 84 | 'use ReactDOM.unmountComponentAtNode instead' 85 | ) 86 | }] 87 | }, { 88 | code: 'React.findDOMNode(instance);', 89 | errors: [{ 90 | message: 'React.findDOMNode is deprecated since React 0.14.0, use ReactDOM.findDOMNode instead' 91 | }] 92 | }, { 93 | code: 'React.renderToString(element);', 94 | errors: [{ 95 | message: 'React.renderToString is deprecated since React 0.14.0, use ReactDOMServer.renderToString instead' 96 | }] 97 | }, { 98 | code: 'React.renderToStaticMarkup(element);', 99 | errors: [{ 100 | message: ( 101 | 'React.renderToStaticMarkup is deprecated since React 0.14.0, ' + 102 | 'use ReactDOMServer.renderToStaticMarkup instead' 103 | ) 104 | }] 105 | }] 106 | 107 | }); 108 | -------------------------------------------------------------------------------- /tests/lib/rules/jsx-first-prop-new-line.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Ensure proper position of the first property in JSX 3 | * @author Joachim Seminck 4 | */ 5 | 'use strict'; 6 | 7 | // ----------------------------------------------------------------------------- 8 | // Requirements 9 | // ----------------------------------------------------------------------------- 10 | 11 | var rule = require('../../../lib/rules/jsx-first-prop-new-line'); 12 | var RuleTester = require('eslint').RuleTester; 13 | 14 | var parserOptions = 'babel-eslint'; 15 | 16 | // ----------------------------------------------------------------------------- 17 | // Tests 18 | // ----------------------------------------------------------------------------- 19 | 20 | var ruleTester = new RuleTester(); 21 | ruleTester.run('jsx-first-prop-new-line', rule, { 22 | 23 | valid: [ 24 | { 25 | code: '', 26 | options: ['never'], 27 | parser: parserOptions 28 | }, 29 | { 30 | code: '', 31 | options: ['never'], 32 | parser: parserOptions 33 | }, 34 | { 35 | code: '', 36 | options: ['never'], 37 | parser: parserOptions 38 | }, 39 | { 40 | code: '', 41 | options: ['never'], 42 | parser: parserOptions 43 | }, 44 | { 45 | code: [ 46 | '' 49 | ].join('\n'), 50 | options: ['never'], 51 | parser: parserOptions 52 | }, 53 | { 54 | code: '', 55 | options: ['multiline'], 56 | parser: parserOptions 57 | }, 58 | { 59 | code: '', 60 | options: ['multiline'], 61 | parser: parserOptions 62 | }, 63 | { 64 | code: '', 65 | options: ['multiline'], 66 | parser: parserOptions 67 | }, 68 | { 69 | code: '', 70 | options: ['multiline'], 71 | parser: parserOptions 72 | }, 73 | { 74 | code: [ 75 | '' 79 | ].join('\n'), 80 | options: ['multiline'], 81 | parser: parserOptions 82 | }, 83 | { 84 | code: [ 85 | '' 89 | ].join('\n'), 90 | options: ['multiline'], 91 | parser: parserOptions 92 | }, 93 | { 94 | code: '', 95 | options: ['always'], 96 | parser: parserOptions 97 | }, 98 | { 99 | code: [ 100 | '' 104 | ].join('\n'), 105 | options: ['always'], 106 | parser: parserOptions 107 | }, 108 | { 109 | code: [ 110 | '' 114 | ].join('\n'), 115 | options: ['always'], 116 | parser: parserOptions 117 | } 118 | ], 119 | 120 | invalid: [ 121 | { 122 | code: '', 123 | options: ['always'], 124 | errors: [{message: 'Property should be placed on a new line'}], 125 | parser: parserOptions 126 | }, 127 | { 128 | code: [ 129 | '' 132 | ].join('\n'), 133 | options: ['always'], 134 | errors: [{message: 'Property should be placed on a new line'}], 135 | parser: parserOptions 136 | }, 137 | { 138 | code: [ 139 | '' 143 | ].join('\n'), 144 | options: ['never'], 145 | errors: [{message: 'Property should be placed on the same line as the component declaration'}], 146 | parser: parserOptions 147 | } 148 | ] 149 | }); 150 | -------------------------------------------------------------------------------- /tests/lib/rules/jsx-space-before-closing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Validate spacing before closing bracket in JSX. 3 | * @author ryym 4 | */ 5 | 'use strict'; 6 | 7 | // ------------------------------------------------------------------------------ 8 | // Requirements 9 | // ------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../lib/rules/jsx-space-before-closing'); 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('jsx-space-before-closing', rule, { 27 | valid: [{ 28 | code: '', 29 | parserOptions: parserOptions 30 | }, { 31 | code: '', 32 | parserOptions: parserOptions 33 | }, { 34 | code: '', 35 | parserOptions: parserOptions 36 | }, { 37 | code: '', 38 | parserOptions: parserOptions 39 | }, { 40 | code: '', 41 | parserOptions: parserOptions 42 | }, { 43 | code: [ 44 | '' 47 | ].join('\n'), 48 | parserOptions: parserOptions 49 | }, { 50 | code: '', 51 | options: ['never'], 52 | parserOptions: parserOptions 53 | }, { 54 | code: '', 55 | options: ['never'], 56 | parserOptions: parserOptions 57 | }, { 58 | code: '', 59 | options: ['never'], 60 | parserOptions: parserOptions 61 | }, { 62 | code: '', 63 | options: ['never'], 64 | parserOptions: parserOptions 65 | }, { 66 | code: '', 67 | options: ['never'], 68 | parserOptions: parserOptions 69 | }, { 70 | code: [ 71 | '' 74 | ].join('\n'), 75 | options: ['never'], 76 | parserOptions: parserOptions 77 | }], 78 | 79 | invalid: [{ 80 | code: '', 81 | output: '', 82 | parserOptions: parserOptions, 83 | errors: [ 84 | {message: 'A space is required before closing bracket'} 85 | ] 86 | }, { 87 | code: '', 88 | output: '', 89 | parserOptions: parserOptions, 90 | errors: [ 91 | {message: 'A space is required before closing bracket'} 92 | ] 93 | }, { 94 | code: '', 95 | output: '', 96 | parserOptions: parserOptions, 97 | errors: [ 98 | {message: 'A space is required before closing bracket'} 99 | ] 100 | }, { 101 | code: '', 102 | output: '', 103 | parserOptions: parserOptions, 104 | errors: [ 105 | {message: 'A space is required before closing bracket'} 106 | ] 107 | }, { 108 | code: '', 109 | output: '', 110 | options: ['never'], 111 | parserOptions: parserOptions, 112 | errors: [ 113 | {message: 'A space is forbidden before closing bracket'} 114 | ] 115 | }, { 116 | code: '', 117 | output: '', 118 | options: ['never'], 119 | parserOptions: parserOptions, 120 | errors: [ 121 | {message: 'A space is forbidden before closing bracket'} 122 | ] 123 | }, { 124 | code: '', 125 | output: '', 126 | options: ['never'], 127 | parserOptions: parserOptions, 128 | errors: [ 129 | {message: 'A space is forbidden before closing bracket'} 130 | ] 131 | }, { 132 | code: '', 133 | output: '', 134 | options: ['never'], 135 | parserOptions: parserOptions, 136 | errors: [ 137 | {message: 'A space is forbidden before closing bracket'} 138 | ] 139 | }] 140 | }); 141 | -------------------------------------------------------------------------------- /docs/rules/jsx-no-bind.md: -------------------------------------------------------------------------------- 1 | # No `.bind()` or Arrow Functions in JSX Props 2 | 3 | A `bind` call or [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) in a JSX prop will create a brand new function on every single render. This is bad for performance, as it will result in the garbage collector being invoked way more than is necessary. 4 | 5 | ## Rule Details 6 | 7 | The following patterns are considered warnings: 8 | 9 | ```js 10 |
11 | ``` 12 | ```js 13 |
console.log('Hello!'))}>
14 | ``` 15 | 16 | The following patterns are not considered warnings: 17 | ```js 18 |
19 | ``` 20 | 21 | ## Rule Options 22 | 23 | ```js 24 | "jsx-no-bind": [, { 25 | "ignoreRefs": || false, 26 | "allowArrowFunctions": || false, 27 | "allowBind": || false 28 | }] 29 | ``` 30 | 31 | ### `ignoreRefs` 32 | 33 | When `true` the following are not considered warnings: 34 | 35 | ```jsx 36 |
this._div = c} /> 37 |
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 | --------------------------------------------------------------------------------