Izmjenjena struktura, dodan backand

This commit is contained in:
GotPPay
2017-10-16 11:19:46 +02:00
parent 1ec88afacb
commit 048e32c4aa
37153 changed files with 2975854 additions and 1 deletions

1753
web/node_modules/eslint-plugin-react/CHANGELOG.md generated vendored Executable file

File diff suppressed because it is too large Load Diff

22
web/node_modules/eslint-plugin-react/LICENSE generated vendored Executable file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014 Yannick Croissant
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

231
web/node_modules/eslint-plugin-react/README.md generated vendored Executable file
View File

@@ -0,0 +1,231 @@
ESLint-plugin-React
===================
[![Maintenance Status][status-image]][status-url] [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Build Status][appveyor-image]][appveyor-url] [![Dependency Status][deps-image]][deps-url] [![Coverage Status][coverage-image]][coverage-url] [![Code Climate][climate-image]][climate-url]
React specific linting rules for ESLint
# Installation
Install [ESLint](https://www.github.com/eslint/eslint) either locally or globally.
```sh
$ npm install eslint --save-dev
```
If you installed `ESLint` globally, you have to install React plugin globally too. Otherwise, install it locally.
```sh
$ npm install eslint-plugin-react --save-dev
```
# Configuration
Add `plugins` section and specify ESLint-plugin-React as a plugin.
```json
{
"plugins": [
"react"
]
}
```
You can also specify some settings that will be shared across all the plugin rules.
```json5
{
"settings": {
"react": {
"createClass": "createReactClass", // Regex for Component Factory to use, default to "createReactClass"
"pragma": "React", // Pragma to use, default to "React"
"version": "15.0" // React version, default to the latest React stable release
}
}
}
```
If it is not already the case you must also configure `ESLint` to support JSX.
With ESLint 1.x.x:
```json
{
"ecmaFeatures": {
"jsx": true
}
}
```
With ESLint 2.x.x or 3.x.x:
```json
{
"parserOptions": {
"ecmaFeatures": {
"jsx": true
}
}
}
```
Finally, enable all of the rules that you would like to use. Use [our preset](#recommended) to get reasonable defaults quickly, and/or choose your own:
```json
"rules": {
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
}
```
# List of supported rules
* [react/display-name](docs/rules/display-name.md): Prevent missing `displayName` in a React component definition
* [react/forbid-component-props](docs/rules/forbid-component-props.md): Forbid certain props on Components
* [react/forbid-elements](docs/rules/forbid-elements.md): Forbid certain elements
* [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes
* [react/forbid-foreign-prop-types](docs/rules/forbid-foreign-prop-types.md): Forbid foreign propTypes
* [react/no-array-index-key](docs/rules/no-array-index-key.md): Prevent using Array index in `key` props
* [react/no-children-prop](docs/rules/no-children-prop.md): Prevent passing children as props
* [react/no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties
* [react/no-danger-with-children](docs/rules/no-danger-with-children.md): Prevent problem with children and props.dangerouslySetInnerHTML
* [react/no-deprecated](docs/rules/no-deprecated.md): Prevent usage of deprecated methods
* [react/no-did-mount-set-state](docs/rules/no-did-mount-set-state.md): Prevent usage of `setState` in `componentDidMount`
* [react/no-did-update-set-state](docs/rules/no-did-update-set-state.md): Prevent usage of `setState` in `componentDidUpdate`
* [react/no-direct-mutation-state](docs/rules/no-direct-mutation-state.md): Prevent direct mutation of `this.state`
* [react/no-find-dom-node](docs/rules/no-find-dom-node.md): Prevent usage of `findDOMNode`
* [react/no-is-mounted](docs/rules/no-is-mounted.md): Prevent usage of `isMounted`
* [react/no-multi-comp](docs/rules/no-multi-comp.md): Prevent multiple component definition per file
* [react/no-render-return-value](docs/rules/no-render-return-value.md): Prevent usage of the return value of `React.render`
* [react/no-set-state](docs/rules/no-set-state.md): Prevent usage of `setState`
* [react/no-string-refs](docs/rules/no-string-refs.md): Prevent using string references in `ref` attribute.
* [react/no-unescaped-entities](docs/rules/no-unescaped-entities.md): Prevent invalid characters from appearing in markup
* [react/no-unknown-property](docs/rules/no-unknown-property.md): Prevent usage of unknown DOM property (fixable)
* [react/no-unused-prop-types](docs/rules/no-unused-prop-types.md): Prevent definitions of unused prop types
* [react/no-will-update-set-state](docs/rules/no-will-update-set-state.md): Prevent usage of `setState` in `componentWillUpdate`
* [react/prefer-es6-class](docs/rules/prefer-es6-class.md): Enforce ES5 or ES6 class for React Components
* [react/prefer-stateless-function](docs/rules/prefer-stateless-function.md): Enforce stateless React Components to be written as a pure function
* [react/prop-types](docs/rules/prop-types.md): Prevent missing props validation in a React component definition
* [react/react-in-jsx-scope](docs/rules/react-in-jsx-scope.md): Prevent missing `React` when using JSX
* [react/require-default-props](docs/rules/require-default-props.md): Enforce a defaultProps definition for every prop that is not a required prop
* [react/require-optimization](docs/rules/require-optimization.md): Enforce React components to have a shouldComponentUpdate method
* [react/require-render-return](docs/rules/require-render-return.md): Enforce ES5 or ES6 class for returning value in render function
* [react/self-closing-comp](docs/rules/self-closing-comp.md): Prevent extra closing tags for components without children (fixable)
* [react/sort-comp](docs/rules/sort-comp.md): Enforce component methods order
* [react/sort-prop-types](docs/rules/sort-prop-types.md): Enforce propTypes declarations alphabetical sorting
* [react/style-prop-object](docs/rules/style-prop-object.md): Enforce style prop value being an object
* [react/void-dom-elements-no-children](docs/rules/void-dom-elements-no-children.md): Prevent void DOM elements (e.g. `<img />`, `<br />`) from receiving children
## JSX-specific rules
* [react/jsx-boolean-value](docs/rules/jsx-boolean-value.md): Enforce boolean attributes notation in JSX (fixable)
* [react/jsx-closing-bracket-location](docs/rules/jsx-closing-bracket-location.md): Validate closing bracket location in JSX (fixable)
* [react/jsx-curly-spacing](docs/rules/jsx-curly-spacing.md): Enforce or disallow spaces inside of curly braces in JSX attributes (fixable)
* [react/jsx-equals-spacing](docs/rules/jsx-equals-spacing.md): Enforce or disallow spaces around equal signs in JSX attributes (fixable)
* [react/jsx-filename-extension](docs/rules/jsx-filename-extension.md): Restrict file extensions that may contain JSX
* [react/jsx-first-prop-new-line](docs/rules/jsx-first-prop-new-line.md): Enforce position of the first prop in JSX (fixable)
* [react/jsx-handler-names](docs/rules/jsx-handler-names.md): Enforce event handler naming conventions in JSX
* [react/jsx-indent](docs/rules/jsx-indent.md): Validate JSX indentation (fixable)
* [react/jsx-indent-props](docs/rules/jsx-indent-props.md): Validate props indentation in JSX (fixable)
* [react/jsx-key](docs/rules/jsx-key.md): Validate JSX has key prop when in array or iterator
* [react/jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md): Limit maximum of props on a single line in JSX
* [react/jsx-no-bind](docs/rules/jsx-no-bind.md): Prevent usage of `.bind()` and arrow functions in JSX props
* [react/jsx-no-comment-textnodes](docs/rules/jsx-no-comment-textnodes.md): Prevent comments from being inserted as text nodes
* [react/jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md): Prevent duplicate props in JSX
* [react/jsx-no-literals](docs/rules/jsx-no-literals.md): Prevent usage of unwrapped JSX strings
* [react/jsx-no-target-blank](docs/rules/jsx-no-target-blank.md): Prevent usage of unsafe `target='_blank'`
* [react/jsx-no-undef](docs/rules/jsx-no-undef.md): Disallow undeclared variables in JSX
* [react/jsx-pascal-case](docs/rules/jsx-pascal-case.md): Enforce PascalCase for user-defined JSX components
* [react/jsx-sort-props](docs/rules/jsx-sort-props.md): Enforce props alphabetical sorting
* [react/jsx-space-before-closing](docs/rules/jsx-space-before-closing.md): Validate spacing before closing bracket in JSX (fixable)
* [react/jsx-tag-spacing](docs/rules/jsx-tag-spacing.md): Validate whitespace in and around the JSX opening and closing brackets (fixable)
* [react/jsx-uses-react](docs/rules/jsx-uses-react.md): Prevent React to be incorrectly marked as unused
* [react/jsx-uses-vars](docs/rules/jsx-uses-vars.md): Prevent variables used in JSX to be incorrectly marked as unused
* [react/jsx-wrap-multilines](docs/rules/jsx-wrap-multilines.md): Prevent missing parentheses around multilines JSX (fixable)
## Other useful plugins
- JSX accessibility: [eslint-plugin-jsx-a11y](https://github.com/evcohen/eslint-plugin-jsx-a11y)
- React Native: [eslint-plugin-react-native](https://github.com/Intellicode/eslint-plugin-react-native)
# Shareable configurations
## Recommended
This plugin exports a `recommended` configuration that enforce React good practices.
To enable this configuration use the `extends` property in your `.eslintrc` config file:
```json
{
"extends": ["eslint:recommended", "plugin:react/recommended"]
}
```
See [ESLint documentation](http://eslint.org/docs/user-guide/configuring#extending-configuration-files) for more information about extending configuration files.
The rules enabled in this configuration are:
* [react/display-name](docs/rules/display-name.md)
* [react/jsx-key](docs/rules/jsx-key.md)
* [react/jsx-no-comment-textnodes](docs/rules/jsx-no-comment-textnodes.md)
* [react/jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md)
* [react/jsx-no-target-blank](docs/rules/jsx-no-target-blank.md)
* [react/jsx-no-undef](docs/rules/jsx-no-undef.md)
* [react/jsx-uses-react](docs/rules/jsx-uses-react.md)
* [react/jsx-uses-vars](docs/rules/jsx-uses-vars.md)
* [react/no-children-prop](docs/rules/no-children-prop.md)
* [react/no-danger-with-children](docs/rules/no-danger-with-children.md)
* [react/no-deprecated](docs/rules/no-deprecated.md)
* [react/no-direct-mutation-state](docs/rules/no-direct-mutation-state.md)
* [react/no-find-dom-node](docs/rules/no-find-dom-node.md)
* [react/no-is-mounted](docs/rules/no-is-mounted.md)
* [react/no-render-return-value](docs/rules/no-render-return-value.md)
* [react/no-string-refs](docs/rules/no-string-refs.md)
* [react/no-unescaped-entities](docs/rules/no-unescaped-entities.md)
* [react/no-unknown-property](docs/rules/no-unknown-property.md)
* [react/prop-types](docs/rules/prop-types.md)
* [react/react-in-jsx-scope](docs/rules/react-in-jsx-scope.md)
* [react/require-render-return](docs/rules/require-render-return.md)
## All
This plugin also exports an `all` configuration that includes every available rule.
This pairs well with the `eslint:all` rule.
```json
{
"plugins": [
"react"
],
"extends": ["eslint:all", "plugin:react/all"]
}
```
**Note**: These configurations will import `eslint-plugin-react` and enable JSX in [parser options](http://eslint.org/docs/user-guide/configuring#specifying-parser-options).
# License
ESLint-plugin-React is licensed under the [MIT License](http://www.opensource.org/licenses/mit-license.php).
[npm-url]: https://npmjs.org/package/eslint-plugin-react
[npm-image]: https://img.shields.io/npm/v/eslint-plugin-react.svg
[travis-url]: https://travis-ci.org/yannickcr/eslint-plugin-react
[travis-image]: https://img.shields.io/travis/yannickcr/eslint-plugin-react/master.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSItMTQyLjUgLTE0Mi41IDI4NSAyODUiPjxjaXJjbGUgcj0iMTQxLjciIGZpbGw9IiNERDQ4MTQiLz48ZyBpZD0iYSIgZmlsbD0iI0ZGRiI%2BPGNpcmNsZSBjeD0iLTk2LjQiIHI9IjE4LjkiLz48cGF0aCBkPSJNLTQ1LjYgNjguNGMtMTYuNi0xMS0yOS0yOC0zNC00Ny44IDYtNSA5LjgtMTIuMyA5LjgtMjAuNnMtMy44LTE1LjctOS44LTIwLjZjNS0xOS44IDE3LjQtMzYuNyAzNC00Ny44bDEzLjggMjMuMkMtNDYtMzUuMi01NS4zLTE4LjctNTUuMyAwYzAgMTguNyA5LjMgMzUuMiAyMy41IDQ1LjJ6Ii8%2BPC9nPjx1c2UgeGxpbms6aHJlZj0iI2EiIHRyYW5zZm9ybT0icm90YXRlKDEyMCkiLz48dXNlIHhsaW5rOmhyZWY9IiNhIiB0cmFuc2Zvcm09InJvdGF0ZSgyNDApIi8%2BPC9zdmc%2B
[appveyor-url]: https://ci.appveyor.com/project/yannickcr/eslint-plugin-react
[appveyor-image]: https://img.shields.io/appveyor/ci/yannickcr/eslint-plugin-react/master.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjEyOCIgaGVpZ2h0PSIxMjgiIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48ZyBmaWxsPSIjMUJBMUUyIiB0cmFuc2Zvcm09InNjYWxlKDgpIj48cGF0aCBkPSJNMCAyLjI2NWw2LjUzOS0uODg4LjAwMyA2LjI4OC02LjUzNi4wMzd6Ii8%2BPHBhdGggZD0iTTYuNTM2IDguMzlsLjAwNSA2LjI5My02LjUzNi0uODk2di01LjQ0eiIvPjxwYXRoIGQ9Ik03LjMyOCAxLjI2MWw4LjY3LTEuMjYxdjcuNTg1bC04LjY3LjA2OXoiLz48cGF0aCBkPSJNMTYgOC40NDlsLS4wMDIgNy41NTEtOC42Ny0xLjIyLS4wMTItNi4zNDV6Ii8%2BPC9nPjwvc3ZnPg==
[deps-url]: https://david-dm.org/yannickcr/eslint-plugin-react
[deps-image]: https://img.shields.io/david/dev/yannickcr/eslint-plugin-react.svg
[coverage-url]: https://coveralls.io/r/yannickcr/eslint-plugin-react?branch=master
[coverage-image]: https://img.shields.io/coveralls/yannickcr/eslint-plugin-react/master.svg
[climate-url]: https://codeclimate.com/github/yannickcr/eslint-plugin-react
[climate-image]: https://img.shields.io/codeclimate/github/yannickcr/eslint-plugin-react.svg
[status-url]: https://github.com/yannickcr/eslint-plugin-react/pulse
[status-image]: https://img.shields.io/badge/status-maintained-brightgreen.svg

146
web/node_modules/eslint-plugin-react/index.js generated vendored Executable file
View File

@@ -0,0 +1,146 @@
'use strict';
var has = require('has');
var allRules = {
'jsx-uses-react': require('./lib/rules/jsx-uses-react'),
'no-multi-comp': require('./lib/rules/no-multi-comp'),
'prop-types': require('./lib/rules/prop-types'),
'require-default-props': require('./lib/rules/require-default-props'),
'display-name': require('./lib/rules/display-name'),
'jsx-wrap-multilines': require('./lib/rules/jsx-wrap-multilines'),
'self-closing-comp': require('./lib/rules/self-closing-comp'),
'jsx-no-comment-textnodes': require('./lib/rules/jsx-no-comment-textnodes'),
'no-array-index-key': require('./lib/rules/no-array-index-key'),
'no-danger': require('./lib/rules/no-danger'),
'no-set-state': require('./lib/rules/no-set-state'),
'no-is-mounted': require('./lib/rules/no-is-mounted'),
'no-deprecated': require('./lib/rules/no-deprecated'),
'no-did-mount-set-state': require('./lib/rules/no-did-mount-set-state'),
'no-did-update-set-state': require('./lib/rules/no-did-update-set-state'),
'no-render-return-value': require('./lib/rules/no-render-return-value'),
'no-unescaped-entities': require('./lib/rules/no-unescaped-entities'),
'no-will-update-set-state': require('./lib/rules/no-will-update-set-state'),
'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'),
'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'),
'jsx-handler-names': require('./lib/rules/jsx-handler-names'),
'jsx-pascal-case': require('./lib/rules/jsx-pascal-case'),
'jsx-no-bind': require('./lib/rules/jsx-no-bind'),
'jsx-no-undef': require('./lib/rules/jsx-no-undef'),
'no-unknown-property': require('./lib/rules/no-unknown-property'),
'jsx-curly-spacing': require('./lib/rules/jsx-curly-spacing'),
'jsx-equals-spacing': require('./lib/rules/jsx-equals-spacing'),
'jsx-sort-props': require('./lib/rules/jsx-sort-props'),
'sort-prop-types': require('./lib/rules/sort-prop-types'),
'jsx-boolean-value': require('./lib/rules/jsx-boolean-value'),
'sort-comp': require('./lib/rules/sort-comp'),
'jsx-no-duplicate-props': require('./lib/rules/jsx-no-duplicate-props'),
'jsx-max-props-per-line': require('./lib/rules/jsx-max-props-per-line'),
'jsx-no-literals': require('./lib/rules/jsx-no-literals'),
'jsx-indent-props': require('./lib/rules/jsx-indent-props'),
'jsx-indent': require('./lib/rules/jsx-indent'),
'jsx-closing-bracket-location': require('./lib/rules/jsx-closing-bracket-location'),
'jsx-space-before-closing': require('./lib/rules/jsx-space-before-closing'),
'no-direct-mutation-state': require('./lib/rules/no-direct-mutation-state'),
'forbid-component-props': require('./lib/rules/forbid-component-props'),
'forbid-elements': require('./lib/rules/forbid-elements'),
'forbid-prop-types': require('./lib/rules/forbid-prop-types'),
'forbid-foreign-prop-types': require('./lib/rules/forbid-foreign-prop-types'),
'prefer-es6-class': require('./lib/rules/prefer-es6-class'),
'jsx-key': require('./lib/rules/jsx-key'),
'no-string-refs': require('./lib/rules/no-string-refs'),
'prefer-stateless-function': require('./lib/rules/prefer-stateless-function'),
'require-render-return': require('./lib/rules/require-render-return'),
'jsx-first-prop-new-line': require('./lib/rules/jsx-first-prop-new-line'),
'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank'),
'jsx-filename-extension': require('./lib/rules/jsx-filename-extension'),
'require-optimization': require('./lib/rules/require-optimization'),
'no-find-dom-node': require('./lib/rules/no-find-dom-node'),
'no-danger-with-children': require('./lib/rules/no-danger-with-children'),
'style-prop-object': require('./lib/rules/style-prop-object'),
'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'),
'no-children-prop': require('./lib/rules/no-children-prop'),
'void-dom-elements-no-children': require('./lib/rules/void-dom-elements-no-children'),
'jsx-tag-spacing': require('./lib/rules/jsx-tag-spacing')
};
function filterRules(rules, predicate) {
var result = {};
for (var key in rules) {
if (has(rules, key) && predicate(rules[key])) {
result[key] = rules[key];
}
}
return result;
}
function configureAsError(rules) {
var result = {};
for (var key in rules) {
if (!has(rules, key)) {
continue;
}
result[`react/${key}`] = 2;
}
return result;
}
var activeRules = filterRules(allRules, function(rule) {
return !rule.meta.deprecated;
});
var activeRulesConfig = configureAsError(activeRules);
var deprecatedRules = filterRules(allRules, function(rule) {
return rule.meta.deprecated;
});
module.exports = {
deprecatedRules: deprecatedRules,
rules: allRules,
configs: {
recommended: {
plugins: [
'react'
],
parserOptions: {
ecmaFeatures: {
jsx: true
}
},
rules: {
'react/display-name': 2,
'react/jsx-key': 2,
'react/jsx-no-comment-textnodes': 2,
'react/jsx-no-duplicate-props': 2,
'react/jsx-no-target-blank': 2,
'react/jsx-no-undef': 2,
'react/jsx-uses-react': 2,
'react/jsx-uses-vars': 2,
'react/no-children-prop': 2,
'react/no-danger-with-children': 2,
'react/no-deprecated': 2,
'react/no-direct-mutation-state': 2,
'react/no-find-dom-node': 2,
'react/no-is-mounted': 2,
'react/no-render-return-value': 2,
'react/no-string-refs': 2,
'react/no-unescaped-entities': 2,
'react/no-unknown-property': 2,
'react/prop-types': 2,
'react/react-in-jsx-scope': 2,
'react/require-render-return': 2
}
},
all: {
plugins: [
'react'
],
parserOptions: {
ecmaFeatures: {
jsx: true
}
},
rules: activeRulesConfig
}
}
};

View File

@@ -0,0 +1,236 @@
/**
* @fileoverview Prevent missing displayName in a React component definition
* @author Yannick Croissant
*/
'use strict';
var has = require('has');
var Components = require('../util/Components');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent missing displayName in a React component definition',
category: 'Best Practices',
recommended: true
},
schema: [{
type: 'object',
properties: {
ignoreTranspilerName: {
type: 'boolean'
}
},
additionalProperties: false
}]
},
create: Components.detect(function(context, components, utils) {
var sourceCode = context.getSourceCode();
var config = context.options[0] || {};
var ignoreTranspilerName = config.ignoreTranspilerName || false;
var MISSING_MESSAGE = 'Component definition is missing display name';
/**
* Checks if we are declaring a display name
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if we are declaring a display name, false if not.
*/
function isDisplayNameDeclaration(node) {
switch (node.type) {
// Special case for class properties
// (babel-eslint does not expose property name so we have to rely on tokens)
case 'ClassProperty':
var tokens = sourceCode.getFirstTokens(node, 2);
if (
tokens[0].value === 'displayName' ||
(tokens[1] && tokens[1].value === 'displayName')
) {
return true;
}
return false;
case 'Identifier':
return node.name === 'displayName';
case 'Literal':
return node.value === 'displayName';
default:
return false;
}
}
/**
* Mark a prop type as declared
* @param {ASTNode} node The AST node being checked.
*/
function markDisplayNameAsDeclared(node) {
components.set(node, {
hasDisplayName: true
});
}
/**
* Reports missing display name for a given component
* @param {Object} component The component to process
*/
function reportMissingDisplayName(component) {
context.report({
node: component.node,
message: MISSING_MESSAGE,
data: {
component: component.name
}
});
}
/**
* Checks if the component have a name set by the transpiler
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if component has a name, false if not.
*/
function hasTranspilerName(node) {
var namedObjectAssignment = (
node.type === 'ObjectExpression' &&
node.parent &&
node.parent.parent &&
node.parent.parent.type === 'AssignmentExpression' &&
(
!node.parent.parent.left.object ||
node.parent.parent.left.object.name !== 'module' ||
node.parent.parent.left.property.name !== 'exports'
)
);
var namedObjectDeclaration = (
node.type === 'ObjectExpression' &&
node.parent &&
node.parent.parent &&
node.parent.parent.type === 'VariableDeclarator'
);
var namedClass = (
(node.type === 'ClassDeclaration' || node.type === 'ClassExpression') &&
node.id &&
node.id.name
);
var namedFunctionDeclaration = (
(node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') &&
node.id &&
node.id.name
);
var namedFunctionExpression = (
(node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') &&
node.parent &&
(node.parent.type === 'VariableDeclarator' || node.parent.method === true) &&
(!node.parent.parent || !utils.isES5Component(node.parent.parent))
);
if (
namedObjectAssignment || namedObjectDeclaration ||
namedClass ||
namedFunctionDeclaration || namedFunctionExpression
) {
return true;
}
return false;
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
ClassProperty: function(node) {
if (!isDisplayNameDeclaration(node)) {
return;
}
markDisplayNameAsDeclared(node);
},
MemberExpression: function(node) {
if (!isDisplayNameDeclaration(node.property)) {
return;
}
var component = utils.getRelatedComponent(node);
if (!component) {
return;
}
markDisplayNameAsDeclared(component.node);
},
FunctionExpression: function(node) {
if (ignoreTranspilerName || !hasTranspilerName(node)) {
return;
}
markDisplayNameAsDeclared(node);
},
FunctionDeclaration: function(node) {
if (ignoreTranspilerName || !hasTranspilerName(node)) {
return;
}
markDisplayNameAsDeclared(node);
},
ArrowFunctionExpression: function(node) {
if (ignoreTranspilerName || !hasTranspilerName(node)) {
return;
}
markDisplayNameAsDeclared(node);
},
MethodDefinition: function(node) {
if (!isDisplayNameDeclaration(node.key)) {
return;
}
markDisplayNameAsDeclared(node);
},
ClassExpression: function(node) {
if (ignoreTranspilerName || !hasTranspilerName(node)) {
return;
}
markDisplayNameAsDeclared(node);
},
ClassDeclaration: function(node) {
if (ignoreTranspilerName || !hasTranspilerName(node)) {
return;
}
markDisplayNameAsDeclared(node);
},
ObjectExpression: function(node) {
if (ignoreTranspilerName || !hasTranspilerName(node)) {
// Search for the displayName declaration
node.properties.forEach(function(property) {
if (!property.key || !isDisplayNameDeclaration(property.key)) {
return;
}
markDisplayNameAsDeclared(node);
});
return;
}
markDisplayNameAsDeclared(node);
},
'Program:exit': function() {
var list = components.list();
// Report missing display name for all components
for (var component in list) {
if (!has(list, component) || list[component].hasDisplayName) {
continue;
}
reportMissingDisplayName(list[component]);
}
}
};
})
};

View File

@@ -0,0 +1,69 @@
/**
* @fileoverview Forbid certain props on components
* @author Joe Lencioni
*/
'use strict';
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
var DEFAULTS = ['className', 'style'];
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Forbid certain props on components',
category: 'Best Practices',
recommended: false
},
schema: [{
type: 'object',
properties: {
forbid: {
type: 'array',
items: {
type: 'string'
}
}
},
additionalProperties: true
}]
},
create: function(context) {
function isForbidden(prop) {
var configuration = context.options[0] || {};
var forbid = configuration.forbid || DEFAULTS;
return forbid.indexOf(prop) >= 0;
}
return {
JSXAttribute: function(node) {
var tag = node.parent.name.name;
if (tag && tag[0] !== tag[0].toUpperCase()) {
// This is a DOM node, not a Component, so exit.
return;
}
var prop = node.name.name;
if (!isForbidden(prop)) {
return;
}
context.report({
node: node,
message: `Prop \`${prop}\` is forbidden on Components`
});
}
};
}
};

View File

@@ -0,0 +1,112 @@
/**
* @fileoverview Forbid certain elements
* @author Kenneth Chung
*/
'use strict';
var has = require('has');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Forbid certain elements',
category: 'Best Practices',
recommended: false
},
schema: [{
type: 'object',
properties: {
forbid: {
type: 'array',
items: {
anyOf: [
{type: 'string'},
{
type: 'object',
properties: {
element: {type: 'string'},
message: {type: 'string'}
},
required: ['element'],
additionalProperties: false
}
]
}
}
},
additionalProperties: false
}]
},
create: function(context) {
var sourceCode = context.getSourceCode();
var configuration = context.options[0] || {};
var forbidConfiguration = configuration.forbid || [];
var indexedForbidConfigs = {};
forbidConfiguration.forEach(function(item) {
if (typeof item === 'string') {
indexedForbidConfigs[item] = {element: item};
} else {
indexedForbidConfigs[item.element] = item;
}
});
function errorMessageForElement(name) {
var message = `<${name}> is forbidden`;
var additionalMessage = indexedForbidConfigs[name].message;
if (additionalMessage) {
return `${message}, ${additionalMessage}`;
}
return message;
}
function isValidCreateElement(node) {
return node.callee
&& node.callee.type === 'MemberExpression'
&& node.callee.object.name === 'React'
&& node.callee.property.name === 'createElement'
&& node.arguments.length > 0;
}
function reportIfForbidden(element, node) {
if (has(indexedForbidConfigs, element)) {
context.report({
node: node,
message: errorMessageForElement(element)
});
}
}
return {
JSXOpeningElement: function(node) {
reportIfForbidden(sourceCode.getText(node.name), node.name);
},
CallExpression: function(node) {
if (!isValidCreateElement(node)) {
return;
}
var argument = node.arguments[0];
var argType = argument.type;
if (argType === 'Identifier' && /^[A-Z_]/.test(argument.name)) {
reportIfForbidden(argument.name, argument);
} else if (argType === 'Literal' && /^[a-z][^\.]*$/.test(argument.value)) {
reportIfForbidden(argument.value, argument);
} else if (argType === 'MemberExpression') {
reportIfForbidden(sourceCode.getText(argument), argument);
}
}
};
}
};

View File

@@ -0,0 +1,61 @@
/**
* @fileoverview Forbid using another component's propTypes
* @author Ian Christian Myers
*/
'use strict';
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Forbid using another component\'s propTypes',
category: 'Best Practices',
recommended: false
}
},
create: function(context) {
// --------------------------------------------------------------------------
// Helpers
// --------------------------------------------------------------------------
function isLeftSideOfAssignment(node) {
return node.parent.type === 'AssignmentExpression' && node.parent.left === node;
}
return {
MemberExpression: function(node) {
if (!node.computed && node.property && node.property.type === 'Identifier' &&
node.property.name === 'propTypes' && !isLeftSideOfAssignment(node) ||
node.property && node.property.type === 'Literal' &&
node.property.value === 'propTypes' && !isLeftSideOfAssignment(node)) {
context.report({
node: node.property,
message: 'Using another component\'s propTypes is forbidden'
});
}
},
ObjectPattern: function(node) {
var propTypesNode = node.properties.find(function(property) {
return property.type === 'Property' && property.key.name === 'propTypes';
});
if (propTypesNode) {
context.report({
node: propTypesNode,
message: 'Using another component\'s propTypes is forbidden'
});
}
}
};
}
};

View File

@@ -0,0 +1,144 @@
/**
* @fileoverview Forbid certain propTypes
*/
'use strict';
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
var DEFAULTS = ['any', 'array', 'object'];
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Forbid certain propTypes',
category: 'Best Practices',
recommended: false
},
schema: [{
type: 'object',
properties: {
forbid: {
type: 'array',
items: {
type: 'string'
}
}
},
additionalProperties: true
}]
},
create: function(context) {
function isForbidden(type) {
var configuration = context.options[0] || {};
var forbid = configuration.forbid || DEFAULTS;
return forbid.indexOf(type) >= 0;
}
/**
* Checks if node is `propTypes` declaration
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if node is `propTypes` declaration, false if not.
*/
function isPropTypesDeclaration(node) {
// Special case for class properties
// (babel-eslint does not expose property name so we have to rely on tokens)
if (node.type === 'ClassProperty') {
var tokens = context.getFirstTokens(node, 2);
if (tokens[0].value === 'propTypes' || (tokens[1] && tokens[1].value === 'propTypes')) {
return true;
}
return false;
}
return Boolean(
node &&
node.name === 'propTypes'
);
}
/**
* Checks if propTypes declarations are forbidden
* @param {Array} declarations The array of AST nodes being checked.
* @returns {void}
*/
function checkForbidden(declarations) {
declarations.forEach(function(declaration) {
if (declaration.type !== 'Property') {
return;
}
var target;
var value = declaration.value;
if (
value.type === 'MemberExpression' &&
value.property &&
value.property.name &&
value.property.name === 'isRequired'
) {
value = value.object;
}
if (
value.type === 'CallExpression' &&
value.callee.type === 'MemberExpression'
) {
value = value.callee;
}
if (value.property) {
target = value.property.name;
} else if (value.type === 'Identifier') {
target = value.name;
}
if (isForbidden(target)) {
context.report({
node: declaration,
message: `Prop type \`${target}\` is forbidden`
});
}
});
}
return {
ClassProperty: function(node) {
if (isPropTypesDeclaration(node) && node.value && node.value.type === 'ObjectExpression') {
checkForbidden(node.value.properties);
}
},
MemberExpression: function(node) {
if (isPropTypesDeclaration(node.property)) {
var right = node.parent.right;
if (right && right.type === 'ObjectExpression') {
checkForbidden(right.properties);
}
}
},
ObjectExpression: function(node) {
node.properties.forEach(function(property) {
if (!property.key) {
return;
}
if (!isPropTypesDeclaration(property.key)) {
return;
}
if (property.value.type === 'ObjectExpression') {
checkForbidden(property.value.properties);
}
});
}
};
}
};

View File

@@ -0,0 +1,63 @@
/**
* @fileoverview Enforce boolean attributes notation in JSX
* @author Yannick Croissant
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Enforce boolean attributes notation in JSX',
category: 'Stylistic Issues',
recommended: false
},
fixable: 'code',
schema: [{
enum: ['always', 'never']
}]
},
create: function(context) {
var configuration = context.options[0] || 'never';
var NEVER_MESSAGE = 'Value must be omitted for boolean attributes';
var ALWAYS_MESSAGE = 'Value must be set for boolean attributes';
return {
JSXAttribute: function(node) {
switch (configuration) {
case 'always':
if (node.value === null) {
context.report({
node: node,
message: ALWAYS_MESSAGE,
fix: function(fixer) {
return fixer.insertTextAfter(node, '={true}');
}
});
}
break;
case 'never':
if (node.value && node.value.type === 'JSXExpressionContainer' && node.value.expression.value === true) {
context.report({
node: node,
message: NEVER_MESSAGE,
fix: function(fixer) {
return fixer.removeRange([node.name.range[1], node.value.range[1]]);
}
});
}
break;
default:
break;
}
}
};
}
};

View File

@@ -0,0 +1,286 @@
/**
* @fileoverview Validate closing bracket location in JSX
* @author Yannick Croissant
*/
'use strict';
var has = require('has');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Validate closing bracket location in JSX',
category: 'Stylistic Issues',
recommended: false
},
fixable: 'code',
schema: [{
oneOf: [
{
enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned']
},
{
type: 'object',
properties: {
location: {
enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned']
}
},
additionalProperties: false
}, {
type: 'object',
properties: {
nonEmpty: {
enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned', false]
},
selfClosing: {
enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned', false]
}
},
additionalProperties: false
}
]
}]
},
create: function(context) {
var MESSAGE = 'The closing bracket must be {{location}}{{details}}';
var MESSAGE_LOCATION = {
'after-props': 'placed after the last prop',
'after-tag': 'placed after the opening tag',
'props-aligned': 'aligned with the last prop',
'tag-aligned': 'aligned with the opening tag',
'line-aligned': 'aligned with the line containing the opening tag'
};
var DEFAULT_LOCATION = 'tag-aligned';
var sourceCode = context.getSourceCode();
var config = context.options[0];
var options = {
nonEmpty: DEFAULT_LOCATION,
selfClosing: DEFAULT_LOCATION
};
if (typeof config === 'string') {
// simple shorthand [1, 'something']
options.nonEmpty = config;
options.selfClosing = config;
} else if (typeof config === 'object') {
// [1, {location: 'something'}] (back-compat)
if (has(config, 'location')) {
options.nonEmpty = config.location;
options.selfClosing = config.location;
}
// [1, {nonEmpty: 'something'}]
if (has(config, 'nonEmpty')) {
options.nonEmpty = config.nonEmpty;
}
// [1, {selfClosing: 'something'}]
if (has(config, 'selfClosing')) {
options.selfClosing = config.selfClosing;
}
}
/**
* Get expected location for the closing bracket
* @param {Object} tokens Locations of the opening bracket, closing bracket and last prop
* @return {String} Expected location for the closing bracket
*/
function getExpectedLocation(tokens) {
var location;
// Is always after the opening tag if there is no props
if (typeof tokens.lastProp === 'undefined') {
location = 'after-tag';
// Is always after the last prop if this one is on the same line as the opening bracket
} else if (tokens.opening.line === tokens.lastProp.lastLine) {
location = 'after-props';
// Else use configuration dependent on selfClosing property
} else {
location = tokens.selfClosing ? options.selfClosing : options.nonEmpty;
}
return location;
}
/**
* Get the correct 0-indexed column for the closing bracket, given the
* expected location.
* @param {Object} tokens Locations of the opening bracket, closing bracket and last prop
* @param {String} expectedLocation Expected location for the closing bracket
* @return {?Number} The correct column for the closing bracket, or null
*/
function getCorrectColumn(tokens, expectedLocation) {
switch (expectedLocation) {
case 'props-aligned':
return tokens.lastProp.column;
case 'tag-aligned':
return tokens.opening.column;
case 'line-aligned':
return tokens.openingStartOfLine.column;
default:
return null;
}
}
/**
* Check if the closing bracket is correctly located
* @param {Object} tokens Locations of the opening bracket, closing bracket and last prop
* @param {String} expectedLocation Expected location for the closing bracket
* @return {Boolean} True if the closing bracket is correctly located, false if not
*/
function hasCorrectLocation(tokens, expectedLocation) {
switch (expectedLocation) {
case 'after-tag':
return tokens.tag.line === tokens.closing.line;
case 'after-props':
return tokens.lastProp.lastLine === tokens.closing.line;
case 'props-aligned':
case 'tag-aligned':
case 'line-aligned':
var correctColumn = getCorrectColumn(tokens, expectedLocation);
return correctColumn === tokens.closing.column;
default:
return true;
}
}
/**
* Get the characters used for indentation on the line to be matched
* @param {Object} tokens Locations of the opening bracket, closing bracket and last prop
* @param {String} expectedLocation Expected location for the closing bracket
* @param {Number} correctColumn Expected column for the closing bracket
* @return {String} The characters used for indentation
*/
function getIndentation(tokens, expectedLocation, correctColumn) {
var indentation, spaces = [];
switch (expectedLocation) {
case 'props-aligned':
indentation = /^\s*/.exec(sourceCode.lines[tokens.lastProp.firstLine - 1])[0];
break;
case 'tag-aligned':
case 'line-aligned':
indentation = /^\s*/.exec(sourceCode.lines[tokens.opening.line - 1])[0];
break;
default:
indentation = '';
}
if (indentation.length + 1 < correctColumn) {
// Non-whitespace characters were included in the column offset
spaces = new Array(+correctColumn + 1 - indentation.length);
}
return indentation + spaces.join(' ');
}
/**
* Get the locations of the opening bracket, closing bracket, last prop, and
* start of opening line.
* @param {ASTNode} node The node to check
* @return {Object} Locations of the opening bracket, closing bracket, last
* prop and start of opening line.
*/
function getTokensLocations(node) {
var opening = sourceCode.getFirstToken(node).loc.start;
var closing = sourceCode.getLastTokens(node, node.selfClosing ? 2 : 1)[0].loc.start;
var tag = sourceCode.getFirstToken(node.name).loc.start;
var lastProp;
if (node.attributes.length) {
lastProp = node.attributes[node.attributes.length - 1];
lastProp = {
column: sourceCode.getFirstToken(lastProp).loc.start.column,
firstLine: sourceCode.getFirstToken(lastProp).loc.start.line,
lastLine: sourceCode.getLastToken(lastProp).loc.end.line
};
}
var openingLine = sourceCode.lines[opening.line - 1];
var openingStartOfLine = {
column: /^\s*/.exec(openingLine)[0].length,
line: opening.line
};
return {
tag: tag,
opening: opening,
closing: closing,
lastProp: lastProp,
selfClosing: node.selfClosing,
openingStartOfLine: openingStartOfLine
};
}
/**
* Get an unique ID for a given JSXOpeningElement
*
* @param {ASTNode} node The AST node being checked.
* @returns {String} Unique ID (based on its range)
*/
function getOpeningElementId(node) {
return node.range.join(':');
}
var lastAttributeNode = {};
return {
JSXAttribute: function(node) {
lastAttributeNode[getOpeningElementId(node.parent)] = node;
},
JSXSpreadAttribute: function(node) {
lastAttributeNode[getOpeningElementId(node.parent)] = node;
},
'JSXOpeningElement:exit': function(node) {
var attributeNode = lastAttributeNode[getOpeningElementId(node)];
var cachedLastAttributeEndPos = attributeNode ? attributeNode.end : null;
var expectedNextLine;
var tokens = getTokensLocations(node);
var expectedLocation = getExpectedLocation(tokens);
if (hasCorrectLocation(tokens, expectedLocation)) {
return;
}
var data = {location: MESSAGE_LOCATION[expectedLocation], details: ''};
var correctColumn = getCorrectColumn(tokens, expectedLocation);
if (correctColumn !== null) {
expectedNextLine = tokens.lastProp &&
(tokens.lastProp.lastLine === tokens.closing.line);
data.details = ` (expected column ${correctColumn + 1}${expectedNextLine ? ' on the next line)' : ')'}`;
}
context.report({
node: node,
loc: tokens.closing,
message: MESSAGE,
data: data,
fix: function(fixer) {
var closingTag = tokens.selfClosing ? '/>' : '>';
switch (expectedLocation) {
case 'after-tag':
if (cachedLastAttributeEndPos) {
return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end],
(expectedNextLine ? '\n' : '') + closingTag);
}
return fixer.replaceTextRange([node.name.range[1], node.end],
(expectedNextLine ? '\n' : ' ') + closingTag);
case 'after-props':
return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end],
(expectedNextLine ? '\n' : '') + closingTag);
case 'props-aligned':
case 'tag-aligned':
case 'line-aligned':
return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end],
`\n${getIndentation(tokens, expectedLocation, correctColumn)}${closingTag}`);
default:
return true;
}
}
});
}
};
}
};

View File

@@ -0,0 +1,259 @@
/**
* @fileoverview Enforce or disallow spaces inside of curly braces in JSX attributes.
* @author Jamund Ferguson
* @author Brandyn Bennett
* @author Michael Ficarra
* @author Vignesh Anand
* @author Jamund Ferguson
* @author Yannick Croissant
* @author Erik Wendel
*/
'use strict';
var has = require('has');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
var SPACING = {
always: 'always',
never: 'never'
};
var SPACING_VALUES = [SPACING.always, SPACING.never];
module.exports = {
meta: {
docs: {
description: 'Enforce or disallow spaces inside of curly braces in JSX attributes',
category: 'Stylistic Issues',
recommended: false
},
fixable: 'code',
schema: [{
enum: SPACING_VALUES
}, {
type: 'object',
properties: {
allowMultiline: {
type: 'boolean'
},
spacing: {
type: 'object',
properties: {
objectLiterals: {
enum: SPACING_VALUES
}
}
}
},
additionalProperties: false
}]
},
create: function(context) {
var DEFAULT_SPACING = SPACING.never;
var DEFAULT_ALLOW_MULTILINE = true;
var sourceCode = context.getSourceCode();
var baseSpacing = context.options[0] || DEFAULT_SPACING;
var config = context.options[1] || {};
var multiline = has(config, 'allowMultiline') ? config.allowMultiline : DEFAULT_ALLOW_MULTILINE;
var spacingConfig = config.spacing || {};
var objectLiteralSpacing = spacingConfig.objectLiterals || baseSpacing;
// --------------------------------------------------------------------------
// Helpers
// --------------------------------------------------------------------------
/**
* Determines whether two adjacent tokens have a newline between them.
* @param {Object} left - The left token object.
* @param {Object} right - The right token object.
* @returns {boolean} Whether or not there is a newline between the tokens.
*/
function isMultiline(left, right) {
return left.loc.start.line !== right.loc.start.line;
}
/**
* Reports that there shouldn't be a newline after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportNoBeginningNewline(node, token, spacing) {
context.report({
node: node,
loc: token.loc.start,
message: `There should be no newline after '${token.value}'`,
fix: function(fixer) {
var nextToken = sourceCode.getTokenAfter(token);
return fixer.replaceTextRange([token.range[1], nextToken.range[0]], spacing === SPACING.always ? ' ' : '');
}
});
}
/**
* Reports that there shouldn't be a newline before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportNoEndingNewline(node, token, spacing) {
context.report({
node: node,
loc: token.loc.start,
message: `There should be no newline before '${token.value}'`,
fix: function(fixer) {
var previousToken = sourceCode.getTokenBefore(token);
return fixer.replaceTextRange([previousToken.range[1], token.range[0]], spacing === SPACING.always ? ' ' : '');
}
});
}
/**
* Reports that there shouldn't be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportNoBeginningSpace(node, token) {
context.report({
node: node,
loc: token.loc.start,
message: `There should be no space after '${token.value}'`,
fix: function(fixer) {
var nextToken = sourceCode.getTokenAfter(token);
var nextNode = sourceCode.getNodeByRangeIndex(nextToken.range[0]);
var leadingComments = sourceCode.getComments(nextNode).leading;
var rangeEndRef = leadingComments.length ? leadingComments[0] : nextToken;
return fixer.removeRange([token.range[1], rangeEndRef.range[0]]);
}
});
}
/**
* Reports that there shouldn't be a space before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportNoEndingSpace(node, token) {
context.report({
node: node,
loc: token.loc.start,
message: `There should be no space before '${token.value}'`,
fix: function(fixer) {
var previousToken = sourceCode.getTokenBefore(token);
var previousNode = sourceCode.getNodeByRangeIndex(previousToken.range[0]);
var trailingComments = sourceCode.getComments(previousNode).trailing;
var rangeStartRef = trailingComments.length ? trailingComments[trailingComments.length - 1] : previousToken;
return fixer.removeRange([rangeStartRef.range[1], token.range[0]]);
}
});
}
/**
* Reports that there should be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportRequiredBeginningSpace(node, token) {
context.report({
node: node,
loc: token.loc.start,
message: `A space is required after '${token.value}'`,
fix: function(fixer) {
return fixer.insertTextAfter(token, ' ');
}
});
}
/**
* Reports that there should be a space before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportRequiredEndingSpace(node, token) {
context.report({
node: node,
loc: token.loc.start,
message: `A space is required before '${token.value}'`,
fix: function(fixer) {
return fixer.insertTextBefore(token, ' ');
}
});
}
/**
* Determines if spacing in curly braces is valid.
* @param {ASTNode} node The AST node to check.
* @returns {void}
*/
function validateBraceSpacing(node) {
// Only validate attributes
if (node.parent.type === 'JSXElement') {
return;
}
var first = context.getFirstToken(node);
var last = sourceCode.getLastToken(node);
var second = context.getTokenAfter(first, {includeComments: true});
var penultimate = sourceCode.getTokenBefore(last, {includeComments: true});
if (!second) {
second = context.getTokenAfter(first);
var leadingComments = sourceCode.getNodeByRangeIndex(second.range[0]).leadingComments;
second = leadingComments ? leadingComments[0] : second;
}
if (!penultimate) {
penultimate = sourceCode.getTokenBefore(last);
var trailingComments = sourceCode.getNodeByRangeIndex(penultimate.range[0]).trailingComments;
penultimate = trailingComments ? trailingComments[trailingComments.length - 1] : penultimate;
}
var isObjectLiteral = first.value === second.value;
var spacing = isObjectLiteral ? objectLiteralSpacing : baseSpacing;
if (spacing === SPACING.always) {
if (!sourceCode.isSpaceBetweenTokens(first, second)) {
reportRequiredBeginningSpace(node, first);
} else if (!multiline && isMultiline(first, second)) {
reportNoBeginningNewline(node, first, spacing);
}
if (!sourceCode.isSpaceBetweenTokens(penultimate, last)) {
reportRequiredEndingSpace(node, last);
} else if (!multiline && isMultiline(penultimate, last)) {
reportNoEndingNewline(node, last, spacing);
}
} else if (spacing === SPACING.never) {
if (isMultiline(first, second)) {
if (!multiline) {
reportNoBeginningNewline(node, first, spacing);
}
} else if (sourceCode.isSpaceBetweenTokens(first, second)) {
reportNoBeginningSpace(node, first);
}
if (isMultiline(penultimate, last)) {
if (!multiline) {
reportNoEndingNewline(node, last, spacing);
}
} else if (sourceCode.isSpaceBetweenTokens(penultimate, last)) {
reportNoEndingSpace(node, last);
}
}
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
JSXExpressionContainer: validateBraceSpacing,
JSXSpreadAttribute: validateBraceSpacing
};
}
};

View File

@@ -0,0 +1,104 @@
/**
* @fileoverview Disallow or enforce spaces around equal signs in JSX attributes.
* @author ryym
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Disallow or enforce spaces around equal signs in JSX attributes',
category: 'Stylistic Issues',
recommended: false
},
fixable: 'code',
schema: [{
enum: ['always', 'never']
}]
},
create: function(context) {
var config = context.options[0];
var sourceCode = context.getSourceCode();
/**
* Determines a given attribute node has an equal sign.
* @param {ASTNode} attrNode - The attribute node.
* @returns {boolean} Whether or not the attriute node has an equal sign.
*/
function hasEqual(attrNode) {
return attrNode.type !== 'JSXSpreadAttribute' && attrNode.value !== null;
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
JSXOpeningElement: function(node) {
node.attributes.forEach(function(attrNode) {
if (!hasEqual(attrNode)) {
return;
}
var equalToken = sourceCode.getTokenAfter(attrNode.name);
var spacedBefore = sourceCode.isSpaceBetweenTokens(attrNode.name, equalToken);
var spacedAfter = sourceCode.isSpaceBetweenTokens(equalToken, attrNode.value);
switch (config) {
default:
case 'never':
if (spacedBefore) {
context.report({
node: attrNode,
loc: equalToken.loc.start,
message: 'There should be no space before \'=\'',
fix: function(fixer) {
return fixer.removeRange([attrNode.name.range[1], equalToken.start]);
}
});
}
if (spacedAfter) {
context.report({
node: attrNode,
loc: equalToken.loc.start,
message: 'There should be no space after \'=\'',
fix: function(fixer) {
return fixer.removeRange([equalToken.end, attrNode.value.range[0]]);
}
});
}
break;
case 'always':
if (!spacedBefore) {
context.report({
node: attrNode,
loc: equalToken.loc.start,
message: 'A space is required before \'=\'',
fix: function(fixer) {
return fixer.insertTextBefore(equalToken, ' ');
}
});
}
if (!spacedAfter) {
context.report({
node: attrNode,
loc: equalToken.loc.start,
message: 'A space is required after \'=\'',
fix: function(fixer) {
return fixer.insertTextAfter(equalToken, ' ');
}
});
}
break;
}
});
}
};
}
};

View File

@@ -0,0 +1,94 @@
/**
* @fileoverview Restrict file extensions that may contain JSX
* @author Joe Lencioni
*/
'use strict';
var path = require('path');
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
var DEFAULTS = {
extensions: ['.jsx']
};
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Restrict file extensions that may contain JSX',
category: 'Stylistic Issues',
recommended: false
},
schema: [{
type: 'object',
properties: {
extensions: {
type: 'array',
items: {
type: 'string'
}
}
},
additionalProperties: false
}]
},
create: function(context) {
function getExtensionsConfig() {
return context.options[0] && context.options[0].extensions || DEFAULTS.extensions;
}
var invalidExtension;
var invalidNode;
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
JSXElement: function(node) {
var filename = context.getFilename();
if (filename === '<text>') {
return;
}
if (invalidNode) {
return;
}
var allowedExtensions = getExtensionsConfig();
var isAllowedExtension = allowedExtensions.some(function (extension) {
return filename.slice(-extension.length) === extension;
});
if (isAllowedExtension) {
return;
}
invalidNode = node;
invalidExtension = path.extname(filename);
},
'Program:exit': function() {
if (!invalidNode) {
return;
}
context.report({
node: invalidNode,
message: `JSX not allowed in files with extension '${invalidExtension}'`
});
}
};
}
};

View File

@@ -0,0 +1,68 @@
/**
* @fileoverview Ensure proper position of the first property in JSX
* @author Joachim Seminck
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Ensure proper position of the first property in JSX',
category: 'Stylistic Issues',
recommended: false
},
fixable: 'code',
schema: [{
enum: ['always', 'never', 'multiline', 'multiline-multiprop']
}]
},
create: function (context) {
var configuration = context.options[0] || 'multiline-multiprop';
function isMultilineJSX(jsxNode) {
return jsxNode.loc.start.line < jsxNode.loc.end.line;
}
return {
JSXOpeningElement: function (node) {
if (
(configuration === 'multiline' && isMultilineJSX(node)) ||
(configuration === 'multiline-multiprop' && isMultilineJSX(node) && node.attributes.length > 1) ||
(configuration === 'always')
) {
node.attributes.some(function(decl) {
if (decl.loc.start.line === node.loc.start.line) {
context.report({
node: decl,
message: 'Property should be placed on a new line',
fix: function(fixer) {
return fixer.replaceTextRange([node.name.end, decl.start], '\n');
}
});
}
return true;
});
} else if (configuration === 'never' && node.attributes.length > 0) {
var firstNode = node.attributes[0];
if (node.loc.start.line < firstNode.loc.start.line) {
context.report({
node: firstNode,
message: 'Property should be placed on the same line as the component declaration',
fix: function(fixer) {
return fixer.replaceTextRange([node.name.end, firstNode.start], ' ');
}
});
return;
}
}
return;
}
};
}
};

View File

@@ -0,0 +1,74 @@
/**
* @fileoverview Enforce event handler naming conventions in JSX
* @author Jake Marsh
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Enforce event handler naming conventions in JSX',
category: 'Stylistic Issues',
recommended: false
},
schema: [{
type: 'object',
properties: {
eventHandlerPrefix: {
type: 'string'
},
eventHandlerPropPrefix: {
type: 'string'
}
},
additionalProperties: false
}]
},
create: function(context) {
var sourceCode = context.getSourceCode();
var configuration = context.options[0] || {};
var eventHandlerPrefix = configuration.eventHandlerPrefix || 'handle';
var eventHandlerPropPrefix = configuration.eventHandlerPropPrefix || 'on';
var EVENT_HANDLER_REGEX = new RegExp(`^((props\\.${eventHandlerPropPrefix})|((.*\\.)?${eventHandlerPrefix}))[A-Z].*$`);
var PROP_EVENT_HANDLER_REGEX = new RegExp(`^(${eventHandlerPropPrefix}[A-Z].*|ref)$`);
return {
JSXAttribute: function(node) {
if (!node.value || !node.value.expression || !node.value.expression.object) {
return;
}
var propKey = typeof node.name === 'object' ? node.name.name : node.name;
var propValue = sourceCode.getText(node.value.expression).replace(/^this\.|.*::/, '');
if (propKey === 'ref') {
return;
}
var propIsEventHandler = PROP_EVENT_HANDLER_REGEX.test(propKey);
var propFnIsNamedCorrectly = EVENT_HANDLER_REGEX.test(propValue);
if (propIsEventHandler && !propFnIsNamedCorrectly) {
context.report({
node: node,
message: `Handler function for ${propKey} prop key must begin with '${eventHandlerPrefix}'`
});
} else if (propFnIsNamedCorrectly && !propIsEventHandler) {
context.report({
node: node,
message: `Prop key for ${propValue} must begin with '${eventHandlerPropPrefix}'`
});
}
}
};
}
};

View File

@@ -0,0 +1,180 @@
/**
* @fileoverview Validate props indentation in JSX
* @author Yannick Croissant
* This rule has been ported and modified from eslint and nodeca.
* @author Vitaly Puzrin
* @author Gyandeep Singh
* @copyright 2015 Vitaly Puzrin. All rights reserved.
* @copyright 2015 Gyandeep Singh. All rights reserved.
Copyright (C) 2014 by Vitaly Puzrin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the 'Software'), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Validate props indentation in JSX',
category: 'Stylistic Issues',
recommended: false
},
fixable: 'code',
schema: [{
oneOf: [{
enum: ['tab']
}, {
type: 'integer'
}]
}]
},
create: function(context) {
var MESSAGE = 'Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}.';
var extraColumnStart = 0;
var indentType = 'space';
var indentSize = 4;
var sourceCode = context.getSourceCode();
if (context.options.length) {
if (context.options[0] === 'tab') {
indentSize = 1;
indentType = 'tab';
} else if (typeof context.options[0] === 'number') {
indentSize = context.options[0];
indentType = 'space';
}
}
/**
* Reports a given indent violation and properly pluralizes the message
* @param {ASTNode} node Node violating the indent rule
* @param {Number} needed Expected indentation character count
* @param {Number} gotten Indentation character count in the actual node/code
* @param {Object=} loc Error line and column location
*/
function report(node, needed, gotten, loc) {
var msgContext = {
needed: needed,
type: indentType,
characters: needed === 1 ? 'character' : 'characters',
gotten: gotten
};
if (loc) {
context.report({
node: node,
loc: loc,
message: MESSAGE,
data: msgContext
});
} else {
context.report({
node: node,
message: MESSAGE,
data: msgContext,
fix: function(fixer) {
return fixer.replaceTextRange([node.start - node.loc.start.column, node.start],
Array(needed + 1).join(indentType === 'space' ? ' ' : '\t'));
}
});
}
}
/**
* Get node indent
* @param {ASTNode} node Node to examine
* @param {Boolean} byLastLine get indent of node's last line
* @param {Boolean} excludeCommas skip comma on start of line
* @return {Number} Indent
*/
function getNodeIndent(node, byLastLine, excludeCommas) {
byLastLine = byLastLine || false;
excludeCommas = excludeCommas || false;
var src = sourceCode.getText(node, node.loc.start.column + extraColumnStart);
var lines = src.split('\n');
if (byLastLine) {
src = lines[lines.length - 1];
} else {
src = lines[0];
}
var skip = excludeCommas ? ',' : '';
var regExp;
if (indentType === 'space') {
regExp = new RegExp(`^[ ${skip}]+`);
} else {
regExp = new RegExp(`^[\t${skip}]+`);
}
var indent = regExp.exec(src);
return indent ? indent[0].length : 0;
}
/**
* Checks node is the first in its own start line. By default it looks by start line.
* @param {ASTNode} node The node to check
* @param {Boolean} [byEndLocation] Lookup based on start position or end
* @return {Boolean} true if its the first in the its start line
*/
function isNodeFirstInLine(node, byEndLocation) {
var firstToken = byEndLocation === true ? sourceCode.getLastToken(node, 1) : sourceCode.getTokenBefore(node);
var startLine = byEndLocation === true ? node.loc.end.line : node.loc.start.line;
var endLine = firstToken ? firstToken.loc.end.line : -1;
return startLine !== endLine;
}
/**
* Check indent for nodes list
* @param {ASTNode[]} nodes list of node objects
* @param {Number} indent needed indent
* @param {Boolean} excludeCommas skip comma on start of line
*/
function checkNodesIndent(nodes, indent, excludeCommas) {
nodes.forEach(function(node) {
var nodeIndent = getNodeIndent(node, false, excludeCommas);
if (
node.type !== 'ArrayExpression' && node.type !== 'ObjectExpression' &&
nodeIndent !== indent && isNodeFirstInLine(node)
) {
report(node, indent, nodeIndent);
}
});
}
return {
JSXOpeningElement: function(node) {
var elementIndent = getNodeIndent(node);
checkNodesIndent(node.attributes, elementIndent + indentSize);
}
};
}
};

268
web/node_modules/eslint-plugin-react/lib/rules/jsx-indent.js generated vendored Executable file
View File

@@ -0,0 +1,268 @@
/**
* @fileoverview Validate JSX indentation
* @author Yannick Croissant
* This rule has been ported and modified from eslint and nodeca.
* @author Vitaly Puzrin
* @author Gyandeep Singh
* @copyright 2015 Vitaly Puzrin. All rights reserved.
* @copyright 2015 Gyandeep Singh. All rights reserved.
Copyright (C) 2014 by Vitaly Puzrin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the 'Software'), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Validate JSX indentation',
category: 'Stylistic Issues',
recommended: false
},
fixable: 'whitespace',
schema: [{
oneOf: [{
enum: ['tab']
}, {
type: 'integer'
}]
}]
},
create: function(context) {
var MESSAGE = 'Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}.';
var extraColumnStart = 0;
var indentType = 'space';
var indentSize = 4;
var sourceCode = context.getSourceCode();
if (context.options.length) {
if (context.options[0] === 'tab') {
indentSize = 1;
indentType = 'tab';
} else if (typeof context.options[0] === 'number') {
indentSize = context.options[0];
indentType = 'space';
}
}
var indentChar = indentType === 'space' ? ' ' : '\t';
/**
* Responsible for fixing the indentation issue fix
* @param {ASTNode} node Node violating the indent rule
* @param {Number} needed Expected indentation character count
* @returns {Function} function to be executed by the fixer
* @private
*/
function getFixerFunction(node, needed) {
return function(fixer) {
var indent = Array(needed + 1).join(indentChar);
return fixer.replaceTextRange(
[node.start - node.loc.start.column, node.start],
indent
);
};
}
/**
* Reports a given indent violation and properly pluralizes the message
* @param {ASTNode} node Node violating the indent rule
* @param {Number} needed Expected indentation character count
* @param {Number} gotten Indentation character count in the actual node/code
* @param {Object} loc Error line and column location
*/
function report(node, needed, gotten, loc) {
var msgContext = {
needed: needed,
type: indentType,
characters: needed === 1 ? 'character' : 'characters',
gotten: gotten
};
if (loc) {
context.report({
node: node,
loc: loc,
message: MESSAGE,
data: msgContext,
fix: getFixerFunction(node, needed)
});
} else {
context.report({
node: node,
message: MESSAGE,
data: msgContext,
fix: getFixerFunction(node, needed)
});
}
}
/**
* Get node indent
* @param {ASTNode} node Node to examine
* @param {Boolean} byLastLine get indent of node's last line
* @param {Boolean} excludeCommas skip comma on start of line
* @return {Number} Indent
*/
function getNodeIndent(node, byLastLine, excludeCommas) {
byLastLine = byLastLine || false;
excludeCommas = excludeCommas || false;
var src = sourceCode.getText(node, node.loc.start.column + extraColumnStart);
var lines = src.split('\n');
if (byLastLine) {
src = lines[lines.length - 1];
} else {
src = lines[0];
}
var skip = excludeCommas ? ',' : '';
var regExp;
if (indentType === 'space') {
regExp = new RegExp(`^[ ${skip}]+`);
} else {
regExp = new RegExp(`^[\t${skip}]+`);
}
var indent = regExp.exec(src);
return indent ? indent[0].length : 0;
}
/**
* Checks node is the first in its own start line. By default it looks by start line.
* @param {ASTNode} node The node to check
* @return {Boolean} true if its the first in the its start line
*/
function isNodeFirstInLine(node) {
var token = node;
do {
token = sourceCode.getTokenBefore(token);
} while (token.type === 'JSXText' && /^\s*$/.test(token.value));
var startLine = node.loc.start.line;
var endLine = token ? token.loc.end.line : -1;
return startLine !== endLine;
}
/**
* Check if the node is the right member of a logical expression
* @param {ASTNode} node The node to check
* @return {Boolean} true if its the case, false if not
*/
function isRightInLogicalExp(node) {
return (
node.parent &&
node.parent.parent &&
node.parent.parent.type === 'LogicalExpression' &&
node.parent.parent.right === node.parent
);
}
/**
* Check if the node is the alternate member of a conditional expression
* @param {ASTNode} node The node to check
* @return {Boolean} true if its the case, false if not
*/
function isAlternateInConditionalExp(node) {
return (
node.parent &&
node.parent.parent &&
node.parent.parent.type === 'ConditionalExpression' &&
node.parent.parent.alternate === node.parent &&
sourceCode.getTokenBefore(node).value !== '('
);
}
/**
* Check indent for nodes list
* @param {ASTNode} node The node to check
* @param {Number} indent needed indent
* @param {Boolean} excludeCommas skip comma on start of line
*/
function checkNodesIndent(node, indent, excludeCommas) {
var nodeIndent = getNodeIndent(node, false, excludeCommas);
var isCorrectRightInLogicalExp = isRightInLogicalExp(node) && (nodeIndent - indent) === indentSize;
var isCorrectAlternateInCondExp = isAlternateInConditionalExp(node) && (nodeIndent - indent) === 0;
if (
nodeIndent !== indent &&
isNodeFirstInLine(node) &&
!isCorrectRightInLogicalExp &&
!isCorrectAlternateInCondExp
) {
report(node, indent, nodeIndent);
}
}
return {
JSXOpeningElement: function(node) {
var prevToken = sourceCode.getTokenBefore(node);
if (!prevToken) {
return;
}
// Use the parent in a list or an array
if (prevToken.type === 'JSXText' || prevToken.type === 'Punctuator' && prevToken.value === ',') {
prevToken = sourceCode.getNodeByRangeIndex(prevToken.start);
prevToken = prevToken.type === 'Literal' ? prevToken.parent : prevToken;
// Use the first non-punctuator token in a conditional expression
} else if (prevToken.type === 'Punctuator' && prevToken.value === ':') {
do {
prevToken = sourceCode.getTokenBefore(prevToken);
} while (prevToken.type === 'Punctuator');
prevToken = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
while (prevToken.parent && prevToken.parent.type !== 'ConditionalExpression') {
prevToken = prevToken.parent;
}
}
prevToken = prevToken.type === 'JSXExpressionContainer' ? prevToken.expression : prevToken;
var parentElementIndent = getNodeIndent(prevToken);
var indent = (
prevToken.loc.start.line === node.loc.start.line ||
isRightInLogicalExp(node) ||
isAlternateInConditionalExp(node)
) ? 0 : indentSize;
checkNodesIndent(node, parentElementIndent + indent);
},
JSXClosingElement: function(node) {
if (!node.parent) {
return;
}
var peerElementIndent = getNodeIndent(node.parent.openingElement);
checkNodesIndent(node, peerElementIndent);
},
JSXExpressionContainer: function(node) {
if (!node.parent) {
return;
}
var parentNodeIndent = getNodeIndent(node.parent);
checkNodesIndent(node, parentNodeIndent + indentSize);
}
};
}
};

85
web/node_modules/eslint-plugin-react/lib/rules/jsx-key.js generated vendored Executable file
View File

@@ -0,0 +1,85 @@
/**
* @fileoverview Report missing `key` props in iterators/collection literals.
* @author Ben Mosher
*/
'use strict';
// var Components = require('../util/Components');
var hasProp = require('jsx-ast-utils/hasProp');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Report missing `key` props in iterators/collection literals',
category: 'Possible Errors',
recommended: true
},
schema: []
},
create: function(context) {
function checkIteratorElement(node) {
if (node.type === 'JSXElement' && !hasProp(node.openingElement.attributes, 'key')) {
context.report({
node: node,
message: 'Missing "key" prop for element in iterator'
});
}
}
function getReturnStatement(body) {
return body.filter(function(item) {
return item.type === 'ReturnStatement';
})[0];
}
return {
JSXElement: function(node) {
if (hasProp(node.openingElement.attributes, 'key')) {
return;
}
if (node.parent.type === 'ArrayExpression') {
context.report({
node: node,
message: 'Missing "key" prop for element in array'
});
}
},
// Array.prototype.map
CallExpression: function (node) {
if (node.callee && node.callee.type !== 'MemberExpression') {
return;
}
if (node.callee && node.callee.property && node.callee.property.name !== 'map') {
return;
}
var fn = node.arguments[0];
var isFn = fn && fn.type === 'FunctionExpression';
var isArrFn = fn && fn.type === 'ArrowFunctionExpression';
if (isArrFn && fn.body.type === 'JSXElement') {
checkIteratorElement(fn.body);
}
if (isFn || isArrFn) {
if (fn.body.type === 'BlockStatement') {
var returnStatement = getReturnStatement(fn.body.body);
if (returnStatement && returnStatement.argument) {
checkIteratorElement(returnStatement.argument);
}
}
}
}
};
}
};

View File

@@ -0,0 +1,83 @@
/**
* @fileoverview Limit maximum of props on a single line in JSX
* @author Yannick Croissant
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Limit maximum of props on a single line in JSX',
category: 'Stylistic Issues',
recommended: false
},
schema: [{
type: 'object',
properties: {
maximum: {
type: 'integer',
minimum: 1
},
when: {
type: 'string',
enum: ['always', 'multiline']
}
}
}]
},
create: function (context) {
var sourceCode = context.getSourceCode();
var configuration = context.options[0] || {};
var maximum = configuration.maximum || 1;
var when = configuration.when || 'always';
function getPropName(propNode) {
if (propNode.type === 'JSXSpreadAttribute') {
return sourceCode.getText(propNode.argument);
}
return propNode.name.name;
}
return {
JSXOpeningElement: function (node) {
if (!node.attributes.length) {
return;
}
if (when === 'multiline' && node.loc.start.line === node.loc.end.line) {
return;
}
var firstProp = node.attributes[0];
var linePartitionedProps = [[firstProp]];
node.attributes.reduce(function(last, decl) {
if (last.loc.end.line === decl.loc.start.line) {
linePartitionedProps[linePartitionedProps.length - 1].push(decl);
} else {
linePartitionedProps.push([decl]);
}
return decl;
});
linePartitionedProps.forEach(function(propsInLine) {
if (propsInLine.length > maximum) {
var name = getPropName(propsInLine[maximum]);
context.report({
node: propsInLine[maximum],
message: `Prop \`${name}\` must be placed on a new line`
});
}
});
}
};
}
};

109
web/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.js generated vendored Executable file
View File

@@ -0,0 +1,109 @@
/**
* @fileoverview Prevents usage of Function.prototype.bind and arrow functions
* in React component definition.
* @author Daniel Lo Nigro <dan.cx>
*/
'use strict';
var Components = require('../util/Components');
var propName = require('jsx-ast-utils/propName');
// -----------------------------------------------------------------------------
// Rule Definition
// -----------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevents usage of Function.prototype.bind and arrow functions in React component definition',
category: 'Best Practices',
recommended: false
},
schema: [{
type: 'object',
properties: {
allowArrowFunctions: {
default: false,
type: 'boolean'
},
allowBind: {
default: false,
type: 'boolean'
},
ignoreRefs: {
default: false,
type: 'boolean'
}
},
additionalProperties: false
}]
},
create: Components.detect(function(context, components, utils) {
var configuration = context.options[0] || {};
return {
CallExpression: function(node) {
var callee = node.callee;
if (
!configuration.allowBind &&
(callee.type !== 'MemberExpression' || callee.property.name !== 'bind')
) {
return;
}
var ancestors = context.getAncestors(callee).reverse();
for (var i = 0, j = ancestors.length; i < j; i++) {
if (
!configuration.allowBind &&
(ancestors[i].type === 'MethodDefinition' && ancestors[i].key.name === 'render') ||
(ancestors[i].type === 'Property' && ancestors[i].key.name === 'render')
) {
if (utils.isReturningJSX(ancestors[i])) {
context.report({
node: callee,
message: 'JSX props should not use .bind()'
});
}
break;
}
}
},
JSXAttribute: function(node) {
var isRef = configuration.ignoreRefs && propName(node) === 'ref';
if (isRef || !node.value || !node.value.expression) {
return;
}
var valueNode = node.value.expression;
if (
!configuration.allowBind &&
valueNode.type === 'CallExpression' &&
valueNode.callee.type === 'MemberExpression' &&
valueNode.callee.property.name === 'bind'
) {
context.report({
node: node,
message: 'JSX props should not use .bind()'
});
} else if (
!configuration.allowArrowFunctions &&
valueNode.type === 'ArrowFunctionExpression'
) {
context.report({
node: node,
message: 'JSX props should not use arrow functions'
});
} else if (
!configuration.allowBind &&
valueNode.type === 'BindExpression'
) {
context.report({
node: node,
message: 'JSX props should not use ::'
});
}
}
};
})
};

View File

@@ -0,0 +1,48 @@
/**
* @fileoverview Comments inside children section of tag should be placed inside braces.
* @author Ben Vinegar
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Comments inside children section of tag should be placed inside braces',
category: 'Possible Errors',
recommended: true
},
schema: [{
type: 'object',
properties: {},
additionalProperties: false
}]
},
create: function(context) {
function reportLiteralNode(node) {
context.report(node, 'Comments inside children section of tag should be placed inside braces');
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
Literal: function(node) {
if (/^\s*\/(\/|\*)/m.test(node.value)) {
// inside component, e.g. <div>literal</div>
if (node.parent.type !== 'JSXAttribute' &&
node.parent.type !== 'JSXExpressionContainer' &&
node.parent.type.indexOf('JSX') !== -1) {
reportLiteralNode(node);
}
}
}
};
}
};

View File

@@ -0,0 +1,65 @@
/**
* @fileoverview Enforce no duplicate props
* @author Markus Ånöstam
*/
'use strict';
var has = require('has');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Enforce no duplicate props',
category: 'Possible Errors',
recommended: true
},
schema: [{
type: 'object',
properties: {
ignoreCase: {
type: 'boolean'
}
},
additionalProperties: false
}]
},
create: function (context) {
var configuration = context.options[0] || {};
var ignoreCase = configuration.ignoreCase || false;
return {
JSXOpeningElement: function (node) {
var props = {};
node.attributes.forEach(function(decl) {
if (decl.type === 'JSXSpreadAttribute') {
return;
}
var name = decl.name.name;
if (ignoreCase) {
name = name.toLowerCase();
}
if (has(props, name)) {
context.report({
node: decl,
message: 'No duplicate props allowed'
});
} else {
props[name] = 1;
}
});
}
};
}
};

View File

@@ -0,0 +1,56 @@
/**
* @fileoverview Prevent using string literals in React component definition
* @author Caleb Morris
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent using string literals in React component definition',
category: 'Stylistic Issues',
recommended: false
},
schema: [{
type: 'object',
properties: {},
additionalProperties: false
}]
},
create: function(context) {
function reportLiteralNode(node) {
context.report({
node: node,
message: 'Missing JSX expression container around literal string'
});
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
Literal: function(node) {
if (
!/^[\s]+$/.test(node.value) &&
node.parent &&
node.parent.type !== 'JSXExpressionContainer' &&
node.parent.type !== 'JSXAttribute' &&
node.parent.type.indexOf('JSX') !== -1
) {
reportLiteralNode(node);
}
}
};
}
};

View File

@@ -0,0 +1,52 @@
/**
* @fileoverview Forbid target='_blank' attribute
* @author Kevin Miller
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Forbid target="_blank" attribute without rel="noopener noreferrer"',
category: 'Best Practices',
recommended: true
},
schema: []
},
create: function(context) {
return {
JSXAttribute: function(node) {
if (node.parent.name.name !== 'a') {
return;
}
if (
node.name.name === 'target' &&
node.value.type === 'Literal' &&
node.value.value.toLowerCase() === '_blank'
) {
var relFound = false;
var attrs = node.parent.attributes;
for (var idx in attrs) {
if (attrs[idx].name && attrs[idx].name.name === 'rel') {
var tags = attrs[idx].value.type === 'Literal' && attrs[idx].value.value.toLowerCase().split(' ');
if (!tags || (tags.indexOf('noopener') >= 0 && tags.indexOf('noreferrer') >= 0)) {
relFound = true;
break;
}
}
}
if (!relFound) {
context.report(node, 'Using target="_blank" without rel="noopener noreferrer" ' +
'is a security risk: see https://mathiasbynens.github.io/rel-noopener');
}
}
}
};
}
};

View File

@@ -0,0 +1,118 @@
/**
* @fileoverview Disallow undeclared variables in JSX
* @author Yannick Croissant
*/
'use strict';
/**
* Checks if a node name match the JSX tag convention.
* @param {String} name - Name of the node to check.
* @returns {boolean} Whether or not the node name match the JSX tag convention.
*/
var tagConvention = /^[a-z]|\-/;
function isTagName(name) {
return tagConvention.test(name);
}
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Disallow undeclared variables in JSX',
category: 'Possible Errors',
recommended: true
},
schema: [{
type: 'object',
properties: {
allowGlobals: {
type: 'boolean'
}
},
additionalProperties: false
}]
},
create: function(context) {
var config = context.options[0] || {};
var allowGlobals = config.allowGlobals || false;
/**
* Compare an identifier with the variables declared in the scope
* @param {ASTNode} node - Identifier or JSXIdentifier node
* @returns {void}
*/
function checkIdentifierInJSX(node) {
var scope = context.getScope();
var sourceCode = context.getSourceCode();
var sourceType = sourceCode.ast.sourceType;
var variables = scope.variables;
var scopeType = 'global';
var i;
var len;
// Ignore 'this' keyword (also maked as JSXIdentifier when used in JSX)
if (node.name === 'this') {
return;
}
if (!allowGlobals && sourceType === 'module') {
scopeType = 'module';
}
while (scope.type !== scopeType) {
scope = scope.upper;
variables = scope.variables.concat(variables);
}
if (scope.childScopes.length) {
variables = scope.childScopes[0].variables.concat(variables);
// Temporary fix for babel-eslint
if (scope.childScopes[0].childScopes.length) {
variables = scope.childScopes[0].childScopes[0].variables.concat(variables);
}
}
for (i = 0, len = variables.length; i < len; i++) {
if (variables[i].name === node.name) {
return;
}
}
context.report({
node: node,
message: `'${node.name}' is not defined.`
});
}
return {
JSXOpeningElement: function(node) {
switch (node.name.type) {
case 'JSXIdentifier':
node = node.name;
if (isTagName(node.name)) {
return;
}
break;
case 'JSXMemberExpression':
node = node.name;
do {
node = node.object;
} while (node && node.type !== 'JSXIdentifier');
break;
case 'JSXNamespacedName':
node = node.name.namespace;
break;
default:
break;
}
checkIdentifierInJSX(node);
}
};
}
};

View File

@@ -0,0 +1,76 @@
/**
* @fileoverview Enforce PascalCase for user-defined JSX components
* @author Jake Marsh
*/
'use strict';
var elementType = require('jsx-ast-utils/elementType');
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
var PASCAL_CASE_REGEX = /^([A-Z0-9]|[A-Z0-9]+[a-z0-9]+(?:[A-Z0-9]+[a-z0-9]*)*)$/;
var COMPAT_TAG_REGEX = /^[a-z]|\-/;
var ALL_CAPS_TAG_REGEX = /^[A-Z0-9]+$/;
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Enforce PascalCase for user-defined JSX components',
category: 'Stylistic Issues',
recommended: false
},
schema: [{
type: 'object',
properties: {
allowAllCaps: {
type: 'boolean'
},
ignore: {
type: 'array'
}
},
additionalProperties: false
}]
},
create: function(context) {
var configuration = context.options[0] || {};
var allowAllCaps = configuration.allowAllCaps || false;
var ignore = configuration.ignore || [];
return {
JSXOpeningElement: function(node) {
var name = elementType(node);
// Get namespace if the type is JSXNamespacedName or JSXMemberExpression
if (name.indexOf(':') > -1) {
name = name.substring(0, name.indexOf(':'));
} else if (name.indexOf('.') > -1) {
name = name.substring(0, name.indexOf('.'));
}
var isPascalCase = PASCAL_CASE_REGEX.test(name);
var isCompatTag = COMPAT_TAG_REGEX.test(name);
var isAllowedAllCaps = allowAllCaps && ALL_CAPS_TAG_REGEX.test(name);
var isIgnored = ignore.indexOf(name) !== -1;
if (!isPascalCase && !isCompatTag && !isAllowedAllCaps && !isIgnored) {
context.report({
node: node,
message: `Imported JSX component ${name} must be in PascalCase`
});
}
}
};
}
};

View File

@@ -0,0 +1,245 @@
/**
* @fileoverview Enforce props alphabetical sorting
* @author Ilya Volodin, Yannick Croissant
*/
'use strict';
var elementType = require('jsx-ast-utils/elementType');
var propName = require('jsx-ast-utils/propName');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
function isCallbackPropName(name) {
return /^on[A-Z]/.test(name);
}
var COMPAT_TAG_REGEX = /^[a-z]|\-/;
function isDOMComponent(node) {
var name = elementType(node);
// Get namespace if the type is JSXNamespacedName or JSXMemberExpression
if (name.indexOf(':') > -1) {
name = name.substring(0, name.indexOf(':'));
} else if (name.indexOf('.') > -1) {
name = name.substring(0, name.indexOf('.'));
}
return COMPAT_TAG_REGEX.test(name);
}
var RESERVED_PROPS_LIST = [
'children',
'dangerouslySetInnerHTML',
'key',
'ref'
];
function isReservedPropName(name, list) {
return list.indexOf(name) >= 0;
}
/**
* Checks if the `reservedFirst` option is valid
* @param {Object} context The context of the rule
* @param {Boolean|Array<String>} reservedFirst The `reservedFirst` option
* @return {?Function} If an error is detected, a function to generate the error message, otherwise, `undefined`
*/
// eslint-disable-next-line consistent-return
function validateReservedFirstConfig(context, reservedFirst) {
if (reservedFirst) {
if (Array.isArray(reservedFirst)) {
// Only allow a subset of reserved words in customized lists
// eslint-disable-next-line consistent-return
var nonReservedWords = reservedFirst.filter(function(word) {
if (!isReservedPropName(word, RESERVED_PROPS_LIST)) {
return true;
}
});
if (reservedFirst.length === 0) {
return function(decl) {
context.report({
node: decl,
message: 'A customized reserved first list must not be empty'
});
};
} else if (nonReservedWords.length > 0) {
return function(decl) {
context.report({
node: decl,
message: 'A customized reserved first list must only contain a subset of React reserved props.' +
' Remove: {{ nonReservedWords }}',
data: {
nonReservedWords: nonReservedWords.toString()
}
});
};
}
}
}
}
module.exports = {
meta: {
docs: {
description: 'Enforce props alphabetical sorting',
category: 'Stylistic Issues',
recommended: false
},
schema: [{
type: 'object',
properties: {
// Whether callbacks (prefixed with "on") should be listed at the very end,
// after all other props. Supersedes shorthandLast.
callbacksLast: {
type: 'boolean'
},
// Whether shorthand properties (without a value) should be listed first
shorthandFirst: {
type: 'boolean'
},
// Whether shorthand properties (without a value) should be listed last
shorthandLast: {
type: 'boolean'
},
ignoreCase: {
type: 'boolean'
},
// Whether alphabetical sorting should be enforced
noSortAlphabetically: {
type: 'boolean'
},
reservedFirst: {
type: ['array', 'boolean']
}
},
additionalProperties: false
}]
},
create: function(context) {
var configuration = context.options[0] || {};
var ignoreCase = configuration.ignoreCase || false;
var callbacksLast = configuration.callbacksLast || false;
var shorthandFirst = configuration.shorthandFirst || false;
var shorthandLast = configuration.shorthandLast || false;
var noSortAlphabetically = configuration.noSortAlphabetically || false;
var reservedFirst = configuration.reservedFirst || false;
var reservedFirstError = validateReservedFirstConfig(context, reservedFirst);
var reservedList = Array.isArray(reservedFirst) ? reservedFirst : RESERVED_PROPS_LIST;
return {
JSXOpeningElement: function(node) {
// `dangerouslySetInnerHTML` is only "reserved" on DOM components
if (reservedFirst && !isDOMComponent(node)) {
reservedList = reservedList.filter(function(prop) {
return prop !== 'dangerouslySetInnerHTML';
});
}
node.attributes.reduce(function(memo, decl, idx, attrs) {
if (decl.type === 'JSXSpreadAttribute') {
return attrs[idx + 1];
}
var previousPropName = propName(memo);
var currentPropName = propName(decl);
var previousValue = memo.value;
var currentValue = decl.value;
if (ignoreCase) {
previousPropName = previousPropName.toLowerCase();
currentPropName = currentPropName.toLowerCase();
}
if (reservedFirst) {
if (reservedFirstError) {
reservedFirstError(decl);
return memo;
}
var previousIsReserved = isReservedPropName(previousPropName, reservedList);
var currentIsReserved = isReservedPropName(currentPropName, reservedList);
if (previousIsReserved && currentIsReserved) {
if (!noSortAlphabetically && currentPropName < previousPropName) {
context.report({
node: decl,
message: 'Props should be sorted alphabetically'
});
return memo;
}
return decl;
}
if (!previousIsReserved && currentIsReserved) {
context.report({
node: decl,
message: 'Reserved props must be listed before all other props'
});
return memo;
}
return decl;
}
if (callbacksLast) {
var previousIsCallback = isCallbackPropName(previousPropName);
var currentIsCallback = isCallbackPropName(currentPropName);
if (!previousIsCallback && currentIsCallback) {
// Entering the callback prop section
return decl;
}
if (previousIsCallback && !currentIsCallback) {
// Encountered a non-callback prop after a callback prop
context.report({
node: memo,
message: 'Callbacks must be listed after all other props'
});
return memo;
}
}
if (shorthandFirst) {
if (currentValue && !previousValue) {
return decl;
}
if (!currentValue && previousValue) {
context.report({
node: memo,
message: 'Shorthand props must be listed before all other props'
});
return memo;
}
}
if (shorthandLast) {
if (!currentValue && previousValue) {
return decl;
}
if (currentValue && !previousValue) {
context.report({
node: memo,
message: 'Shorthand props must be listed after all other props'
});
return memo;
}
}
if (!noSortAlphabetically && currentPropName < previousPropName) {
context.report({
node: decl,
message: 'Props should be sorted alphabetically'
});
return memo;
}
return decl;
}, node.attributes[0]);
}
};
}
};

View File

@@ -0,0 +1,90 @@
/**
* @fileoverview Validate spacing before closing bracket in JSX.
* @author ryym
* @deprecated
*/
'use strict';
var getTokenBeforeClosingBracket = require('../util/getTokenBeforeClosingBracket');
var isWarnedForDeprecation = false;
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
deprecated: true,
docs: {
description: 'Validate spacing before closing bracket in JSX',
category: 'Stylistic Issues',
recommended: false
},
fixable: 'code',
schema: [{
enum: ['always', 'never']
}]
},
create: function(context) {
var configuration = context.options[0] || 'always';
var sourceCode = context.getSourceCode();
var NEVER_MESSAGE = 'A space is forbidden before closing bracket';
var ALWAYS_MESSAGE = 'A space is required before closing bracket';
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
JSXOpeningElement: function(node) {
if (!node.selfClosing) {
return;
}
var leftToken = getTokenBeforeClosingBracket(node);
var closingSlash = sourceCode.getTokenAfter(leftToken);
if (leftToken.loc.end.line !== closingSlash.loc.start.line) {
return;
}
if (configuration === 'always' && !sourceCode.isSpaceBetweenTokens(leftToken, closingSlash)) {
context.report({
loc: closingSlash.loc.start,
message: ALWAYS_MESSAGE,
fix: function(fixer) {
return fixer.insertTextBefore(closingSlash, ' ');
}
});
} else if (configuration === 'never' && sourceCode.isSpaceBetweenTokens(leftToken, closingSlash)) {
context.report({
loc: closingSlash.loc.start,
message: NEVER_MESSAGE,
fix: function(fixer) {
var previousToken = sourceCode.getTokenBefore(closingSlash);
return fixer.removeRange([previousToken.range[1], closingSlash.range[0]]);
}
});
}
},
Program: function() {
if (isWarnedForDeprecation || /\=-(f|-format)=/.test(process.argv.join('='))) {
return;
}
/* eslint-disable no-console */
console.log('The react/jsx-space-before-closing rule is deprecated. ' +
'Please use the react/jsx-tag-spacing rule with the ' +
'"beforeSelfClosing" option instead.');
/* eslint-enable no-console */
isWarnedForDeprecation = true;
}
};
}
};

View File

@@ -0,0 +1,236 @@
/**
* @fileoverview Validates whitespace in and around the JSX opening and closing brackets
* @author Diogo Franco (Kovensky)
*/
'use strict';
var has = require('has');
var getTokenBeforeClosingBracket = require('../util/getTokenBeforeClosingBracket');
// ------------------------------------------------------------------------------
// Validators
// ------------------------------------------------------------------------------
function validateClosingSlash(context, node, option) {
var sourceCode = context.getSourceCode();
var SELF_CLOSING_NEVER_MESSAGE = 'Whitespace is forbidden between `/` and `>`; write `/>`';
var SELF_CLOSING_ALWAYS_MESSAGE = 'Whitespace is required between `/` and `>`; write `/ >`';
var NEVER_MESSAGE = 'Whitespace is forbidden between `<` and `/`; write `</`';
var ALWAYS_MESSAGE = 'Whitespace is required between `<` and `/`; write `< /`';
var adjacent;
if (node.selfClosing) {
var lastTokens = sourceCode.getLastTokens(node, 2);
adjacent = !sourceCode.isSpaceBetweenTokens(lastTokens[0], lastTokens[1]);
if (option === 'never') {
if (!adjacent) {
context.report({
node: node,
loc: {
start: lastTokens[0].loc.start,
end: lastTokens[1].loc.end
},
message: SELF_CLOSING_NEVER_MESSAGE,
fix: function(fixer) {
return fixer.removeRange([lastTokens[0].range[1], lastTokens[1].range[0]]);
}
});
}
} else if (option === 'always' && adjacent) {
context.report({
node: node,
loc: {
start: lastTokens[0].loc.start,
end: lastTokens[1].loc.end
},
message: SELF_CLOSING_ALWAYS_MESSAGE,
fix: function(fixer) {
return fixer.insertTextBefore(lastTokens[1], ' ');
}
});
}
} else {
var firstTokens = sourceCode.getFirstTokens(node, 2);
adjacent = !sourceCode.isSpaceBetweenTokens(firstTokens[0], firstTokens[1]);
if (option === 'never') {
if (!adjacent) {
context.report({
node: node,
loc: {
start: firstTokens[0].loc.start,
end: firstTokens[1].loc.end
},
message: NEVER_MESSAGE,
fix: function(fixer) {
return fixer.removeRange([firstTokens[0].range[1], firstTokens[1].range[0]]);
}
});
}
} else if (option === 'always' && adjacent) {
context.report({
node: node,
loc: {
start: firstTokens[0].loc.start,
end: firstTokens[1].loc.end
},
message: ALWAYS_MESSAGE,
fix: function(fixer) {
return fixer.insertTextBefore(firstTokens[1], ' ');
}
});
}
}
}
function validateBeforeSelfClosing(context, node, option) {
var sourceCode = context.getSourceCode();
var NEVER_MESSAGE = 'A space is forbidden before closing bracket';
var ALWAYS_MESSAGE = 'A space is required before closing bracket';
var leftToken = getTokenBeforeClosingBracket(node);
var closingSlash = sourceCode.getTokenAfter(leftToken);
if (leftToken.loc.end.line !== closingSlash.loc.start.line) {
return;
}
if (option === 'always' && !sourceCode.isSpaceBetweenTokens(leftToken, closingSlash)) {
context.report({
node: node,
loc: closingSlash.loc.start,
message: ALWAYS_MESSAGE,
fix: function(fixer) {
return fixer.insertTextBefore(closingSlash, ' ');
}
});
} else if (option === 'never' && sourceCode.isSpaceBetweenTokens(leftToken, closingSlash)) {
context.report({
node: node,
loc: closingSlash.loc.start,
message: NEVER_MESSAGE,
fix: function(fixer) {
var previousToken = sourceCode.getTokenBefore(closingSlash);
return fixer.removeRange([previousToken.range[1], closingSlash.range[0]]);
}
});
}
}
function validateAfterOpening(context, node, option) {
var sourceCode = context.getSourceCode();
var NEVER_MESSAGE = 'A space is forbidden after opening bracket';
var ALWAYS_MESSAGE = 'A space is required after opening bracket';
var openingToken = sourceCode.getTokenBefore(node.name);
if (option === 'allow-multiline') {
if (openingToken.loc.start.line !== node.name.loc.start.line) {
return;
}
}
var adjacent = !sourceCode.isSpaceBetweenTokens(openingToken, node.name);
if (option === 'never' || option === 'allow-multiline') {
if (!adjacent) {
context.report({
node: node,
loc: {
start: openingToken.loc.start,
end: node.name.loc.start
},
message: NEVER_MESSAGE,
fix: function(fixer) {
return fixer.removeRange([openingToken.range[1], node.name.range[0]]);
}
});
}
} else if (option === 'always' && adjacent) {
context.report({
node: node,
loc: {
start: openingToken.loc.start,
end: node.name.loc.start
},
message: ALWAYS_MESSAGE,
fix: function(fixer) {
return fixer.insertTextBefore(node.name, ' ');
}
});
}
}
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {},
fixable: 'whitespace',
schema: [
{
type: 'object',
properties: {
closingSlash: {
enum: ['always', 'never', 'allow']
},
beforeSelfClosing: {
enum: ['always', 'never', 'allow']
},
afterOpening: {
enum: ['always', 'allow-multiline', 'never', 'allow']
}
},
default: {
closingSlash: 'never',
beforeSelfClosing: 'always',
afterOpening: 'never'
},
additionalProperties: false
}
]
},
create: function (context) {
var options = {
closingSlash: 'never',
beforeSelfClosing: 'always',
afterOpening: 'never'
};
for (var key in options) {
if (has(options, key) && has(context.options[0] || {}, key)) {
options[key] = context.options[0][key];
}
}
return {
JSXOpeningElement: function (node) {
if (options.closingSlash !== 'allow' && node.selfClosing) {
validateClosingSlash(context, node, options.closingSlash);
}
if (options.afterOpening !== 'allow') {
validateAfterOpening(context, node, options.afterOpening);
}
if (options.beforeSelfClosing !== 'allow' && node.selfClosing) {
validateBeforeSelfClosing(context, node, options.beforeSelfClosing);
}
},
JSXClosingElement: function (node) {
if (options.afterOpening !== 'allow') {
validateAfterOpening(context, node, options.afterOpening);
}
if (options.closingSlash !== 'allow') {
validateClosingSlash(context, node, options.closingSlash);
}
}
};
}
};

View File

@@ -0,0 +1,40 @@
/**
* @fileoverview Prevent React to be marked as unused
* @author Glen Mailer
*/
'use strict';
var pragmaUtil = require('../util/pragma');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent React to be marked as unused',
category: 'Best Practices',
recommended: true
},
schema: []
},
create: function(context) {
var pragma = pragmaUtil.getFromContext(context);
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
JSXOpeningElement: function() {
context.markVariableAsUsed(pragma);
}
};
}
};

View File

@@ -0,0 +1,49 @@
/**
* @fileoverview Prevent variables used in JSX to be marked as unused
* @author Yannick Croissant
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent variables used in JSX to be marked as unused',
category: 'Best Practices',
recommended: true
},
schema: []
},
create: function(context) {
return {
JSXOpeningElement: function(node) {
var name;
if (node.name.namespace && node.name.namespace.name) {
// <Foo:Bar>
name = node.name.namespace.name;
} else if (node.name.name) {
// <Foo>
name = node.name.name;
} else if (node.name.object) {
// <Foo...Bar>
var parent = node.name.object;
while (parent.object) {
parent = parent.object;
}
name = parent.name;
} else {
return;
}
context.markVariableAsUsed(name);
}
};
}
};

View File

@@ -0,0 +1,140 @@
/**
* @fileoverview Prevent missing parentheses around multilines JSX
* @author Yannick Croissant
*/
'use strict';
var has = require('has');
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
var DEFAULTS = {
declaration: true,
assignment: true,
return: true,
arrow: true
};
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent missing parentheses around multilines JSX',
category: 'Stylistic Issues',
recommended: false
},
fixable: 'code',
schema: [{
type: 'object',
properties: {
declaration: {
type: 'boolean'
},
assignment: {
type: 'boolean'
},
return: {
type: 'boolean'
},
arrow: {
type: 'boolean'
}
},
additionalProperties: false
}]
},
create: function(context) {
var sourceCode = context.getSourceCode();
function isParenthesised(node) {
var previousToken = sourceCode.getTokenBefore(node);
var nextToken = sourceCode.getTokenAfter(node);
return previousToken && nextToken &&
previousToken.value === '(' && previousToken.range[1] <= node.range[0] &&
nextToken.value === ')' && nextToken.range[0] >= node.range[1];
}
function isMultilines(node) {
return node.loc.start.line !== node.loc.end.line;
}
function check(node) {
if (!node || node.type !== 'JSXElement') {
return;
}
if (!isParenthesised(node) && isMultilines(node)) {
context.report({
node: node,
message: 'Missing parentheses around multilines JSX',
fix: function(fixer) {
return fixer.replaceText(node, `(${sourceCode.getText(node)})`);
}
});
}
}
function isEnabled(type) {
var userOptions = context.options[0] || {};
if (has(userOptions, type)) {
return userOptions[type];
}
return DEFAULTS[type];
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
VariableDeclarator: function(node) {
if (!isEnabled('declaration')) {
return;
}
if (node.init && node.init.type === 'ConditionalExpression') {
check(node.init.consequent);
check(node.init.alternate);
return;
}
check(node.init);
},
AssignmentExpression: function(node) {
if (!isEnabled('assignment')) {
return;
}
if (node.right.type === 'ConditionalExpression') {
check(node.right.consequent);
check(node.right.alternate);
return;
}
check(node.right);
},
ReturnStatement: function(node) {
if (isEnabled('return')) {
check(node.argument);
}
},
'ArrowFunctionExpression:exit': function (node) {
var arrowBody = node.body;
if (isEnabled('arrow') && arrowBody.type !== 'BlockStatement') {
check(arrowBody);
}
}
};
}
};

View File

@@ -0,0 +1,199 @@
/**
* @fileoverview Prevent usage of Array index in keys
* @author Joe Lencioni
*/
'use strict';
var has = require('has');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent usage of Array index in keys',
category: 'Best Practices',
recommended: false
},
schema: []
},
create: function(context) {
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
var indexParamNames = [];
var iteratorFunctionsToIndexParamPosition = {
every: 1,
filter: 1,
find: 1,
findIndex: 1,
forEach: 1,
map: 1,
reduce: 2,
reduceRight: 2,
some: 1
};
var ERROR_MESSAGE = 'Do not use Array index in keys';
function isArrayIndex(node) {
return node.type === 'Identifier'
&& indexParamNames.indexOf(node.name) !== -1;
}
function getMapIndexParamName(node) {
var callee = node.callee;
if (callee.type !== 'MemberExpression') {
return null;
}
if (callee.property.type !== 'Identifier') {
return null;
}
if (!has(iteratorFunctionsToIndexParamPosition, callee.property.name)) {
return null;
}
var firstArg = node.arguments[0];
if (!firstArg) {
return null;
}
var isFunction = [
'ArrowFunctionExpression',
'FunctionExpression'
].indexOf(firstArg.type) !== -1;
if (!isFunction) {
return null;
}
var params = firstArg.params;
var indexParamPosition = iteratorFunctionsToIndexParamPosition[callee.property.name];
if (params.length < indexParamPosition + 1) {
return null;
}
return params[indexParamPosition].name;
}
function getIdentifiersFromBinaryExpression(side) {
if (side.type === 'Identifier') {
return side;
}
if (side.type === 'BinaryExpression') {
// recurse
var left = getIdentifiersFromBinaryExpression(side.left);
var right = getIdentifiersFromBinaryExpression(side.right);
return [].concat(left, right).filter(Boolean);
}
return null;
}
function checkPropValue(node) {
if (isArrayIndex(node)) {
// key={bar}
context.report({
node: node,
message: ERROR_MESSAGE
});
return;
}
if (node.type === 'TemplateLiteral') {
// key={`foo-${bar}`}
node.expressions.filter(isArrayIndex).forEach(function() {
context.report({node: node, message: ERROR_MESSAGE});
});
return;
}
if (node.type === 'BinaryExpression') {
// key={'foo' + bar}
var identifiers = getIdentifiersFromBinaryExpression(node);
identifiers.filter(isArrayIndex).forEach(function() {
context.report({node: node, message: ERROR_MESSAGE});
});
return;
}
}
return {
CallExpression: function(node) {
if (
node.callee
&& node.callee.type === 'MemberExpression'
&& ['createElement', 'cloneElement'].indexOf(node.callee.property.name) !== -1
&& node.arguments.length > 1
) {
// React.createElement
if (!indexParamNames.length) {
return;
}
var props = node.arguments[1];
if (props.type !== 'ObjectExpression') {
return;
}
props.properties.forEach(function (prop) {
if (!prop.key || prop.key.name !== 'key') {
// { ...foo }
// { foo: bar }
return;
}
checkPropValue(prop.value);
});
return;
}
var mapIndexParamName = getMapIndexParamName(node);
if (!mapIndexParamName) {
return;
}
indexParamNames.push(mapIndexParamName);
},
JSXAttribute: function(node) {
if (node.name.name !== 'key') {
// foo={bar}
return;
}
if (!indexParamNames.length) {
// Not inside a call expression that we think has an index param.
return;
}
var value = node.value;
if (value.type !== 'JSXExpressionContainer') {
// key='foo'
return;
}
checkPropValue(value.expression);
},
'CallExpression:exit': function(node) {
var mapIndexParamName = getMapIndexParamName(node);
if (!mapIndexParamName) {
return;
}
indexParamNames.pop();
}
};
}
};

View File

@@ -0,0 +1,69 @@
/**
* @fileoverview Prevent passing of children as props
* @author Benjamin Stepp
*/
'use strict';
// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------
/**
* Checks if the node is a createElement call with a props literal.
* @param {ASTNode} node - The AST node being checked.
* @returns {Boolean} - True if node is a createElement call with a props
* object literal, False if not.
*/
function isCreateElementWithProps(node) {
return node.callee
&& node.callee.type === 'MemberExpression'
&& node.callee.property.name === 'createElement'
&& node.arguments.length > 1
&& node.arguments[1].type === 'ObjectExpression';
}
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent passing of children as props.',
category: 'Best Practices',
recommended: true
},
schema: []
},
create: function(context) {
return {
JSXAttribute: function(node) {
if (node.name.name !== 'children') {
return;
}
context.report({
node: node,
message: 'Do not pass children as props. Instead, nest children between the opening and closing tags.'
});
},
CallExpression: function(node) {
if (!isCreateElementWithProps(node)) {
return;
}
var props = node.arguments[1].properties;
var childrenProp = props.find(function(prop) {
return prop.key && prop.key.name === 'children';
});
if (childrenProp) {
context.report({
node: node,
message: 'Do not pass children as props. Instead, pass them as additional arguments to React.createElement.'
});
}
}
};
}
};

View File

@@ -0,0 +1,122 @@
/**
* @fileoverview Report when a DOM element is using both children and dangerouslySetInnerHTML
* @author David Petersen
*/
'use strict';
var variableUtil = require('../util/variable');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Report when a DOM element is using both children and dangerouslySetInnerHTML',
category: '',
recommended: true
},
schema: [] // no options
},
create: function(context) {
function findSpreadVariable(name) {
return variableUtil.variablesInScope(context).find(function (item) {
return item.name === name;
});
}
/**
* Takes a ObjectExpression and returns the value of the prop if it has it
* @param {object} node - ObjectExpression node
* @param {string} propName - name of the prop to look for
*/
function findObjectProp(node, propName) {
if (!node.properties) {
return false;
}
return node.properties.find(function(prop) {
if (prop.type === 'Property') {
return prop.key.name === propName;
} else if (prop.type === 'ExperimentalSpreadProperty') {
var variable = findSpreadVariable(prop.argument.name);
if (variable && variable.defs[0].node.init) {
return findObjectProp(variable.defs[0].node.init, propName);
}
}
return false;
});
}
/**
* Takes a JSXElement and returns the value of the prop if it has it
* @param {object} node - JSXElement node
* @param {string} propName - name of the prop to look for
*/
function findJsxProp(node, propName) {
var attributes = node.openingElement.attributes;
return attributes.find(function (attribute) {
if (attribute.type === 'JSXSpreadAttribute') {
var variable = findSpreadVariable(attribute.argument.name);
if (variable && variable.defs.length && variable.defs[0].node.init) {
return findObjectProp(variable.defs[0].node.init, propName);
}
}
return attribute.name && attribute.name.name === propName;
});
}
return {
JSXElement: function (node) {
var hasChildren = false;
if (node.children.length) {
hasChildren = true;
} else if (findJsxProp(node, 'children')) {
hasChildren = true;
}
if (
node.openingElement.attributes
&& hasChildren
&& findJsxProp(node, 'dangerouslySetInnerHTML')
) {
context.report(node, 'Only set one of `children` or `props.dangerouslySetInnerHTML`');
}
},
CallExpression: function (node) {
if (
node.callee
&& node.callee.type === 'MemberExpression'
&& node.callee.property.name === 'createElement'
&& node.arguments.length > 1
) {
var hasChildren = false;
var props = node.arguments[1];
if (props.type === 'Identifier') {
var variable = variableUtil.variablesInScope(context).find(function (item) {
return item.name === props.name;
});
if (variable && variable.defs[0].node.init) {
props = variable.defs[0].node.init;
}
}
var dangerously = findObjectProp(props, 'dangerouslySetInnerHTML');
if (node.arguments.length === 2) {
if (findObjectProp(props, 'children')) {
hasChildren = true;
}
} else {
hasChildren = true;
}
if (dangerously && hasChildren) {
context.report(node, 'Only set one of `children` or `props.dangerouslySetInnerHTML`');
}
}
}
};
}
};

78
web/node_modules/eslint-plugin-react/lib/rules/no-danger.js generated vendored Executable file
View File

@@ -0,0 +1,78 @@
/**
* @fileoverview Prevent usage of dangerous JSX props
* @author Scott Andrews
*/
'use strict';
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
var DANGEROUS_MESSAGE = 'Dangerous property \'{{name}}\' found';
var DANGEROUS_PROPERTY_NAMES = [
'dangerouslySetInnerHTML'
];
var DANGEROUS_PROPERTIES = DANGEROUS_PROPERTY_NAMES.reduce(function (props, prop) {
props[prop] = prop;
return props;
}, Object.create(null));
// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------
/**
* Checks if a node name match the JSX tag convention.
* @param {String} name - Name of the node to check.
* @returns {boolean} Whether or not the node name match the JSX tag convention.
*/
var tagConvention = /^[a-z]|\-/;
function isTagName(name) {
return tagConvention.test(name);
}
/**
* Checks if a JSX attribute is dangerous.
* @param {String} name - Name of the attribute to check.
* @returns {boolean} Whether or not the attribute is dnagerous.
*/
function isDangerous(name) {
return name in DANGEROUS_PROPERTIES;
}
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent usage of dangerous JSX props',
category: 'Best Practices',
recommended: false
},
schema: []
},
create: function(context) {
return {
JSXAttribute: function(node) {
if (isTagName(node.parent.name.name) && isDangerous(node.name.name)) {
context.report({
node: node,
message: DANGEROUS_MESSAGE,
data: {
name: node.name.name
}
});
}
}
};
}
};

View File

@@ -0,0 +1,167 @@
/**
* @fileoverview Prevent usage of deprecated methods
* @author Yannick Croissant
* @author Scott Feeney
*/
'use strict';
var has = require('has');
var pragmaUtil = require('../util/pragma');
var versionUtil = require('../util/version');
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
var MODULES = {
react: ['React'],
'react-addons-perf': ['ReactPerf', 'Perf']
};
var DEPRECATED_MESSAGE = '{{oldMethod}} is deprecated since React {{version}}{{newMethod}}';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent usage of deprecated methods',
category: 'Best Practices',
recommended: true
},
schema: []
},
create: function(context) {
var sourceCode = context.getSourceCode();
var pragma = pragmaUtil.getFromContext(context);
function getDeprecated() {
var deprecated = {};
// 0.12.0
deprecated[`${pragma}.renderComponent`] = ['0.12.0', `${pragma}.render`];
deprecated[`${pragma}.renderComponentToString`] = ['0.12.0', `${pragma}.renderToString`];
deprecated[`${pragma}.renderComponentToStaticMarkup`] = ['0.12.0', `${pragma}.renderToStaticMarkup`];
deprecated[`${pragma}.isValidComponent`] = ['0.12.0', `${pragma}.isValidElement`];
deprecated[`${pragma}.PropTypes.component`] = ['0.12.0', `${pragma}.PropTypes.element`];
deprecated[`${pragma}.PropTypes.renderable`] = ['0.12.0', `${pragma}.PropTypes.node`];
deprecated[`${pragma}.isValidClass`] = ['0.12.0'];
deprecated['this.transferPropsTo'] = ['0.12.0', 'spread operator ({...})'];
// 0.13.0
deprecated[`${pragma}.addons.classSet`] = ['0.13.0', 'the npm module classnames'];
deprecated[`${pragma}.addons.cloneWithProps`] = ['0.13.0', `${pragma}.cloneElement`];
// 0.14.0
deprecated[`${pragma}.render`] = ['0.14.0', 'ReactDOM.render'];
deprecated[`${pragma}.unmountComponentAtNode`] = ['0.14.0', 'ReactDOM.unmountComponentAtNode'];
deprecated[`${pragma}.findDOMNode`] = ['0.14.0', 'ReactDOM.findDOMNode'];
deprecated[`${pragma}.renderToString`] = ['0.14.0', 'ReactDOMServer.renderToString'];
deprecated[`${pragma}.renderToStaticMarkup`] = ['0.14.0', 'ReactDOMServer.renderToStaticMarkup'];
// 15.0.0
deprecated[`${pragma}.addons.LinkedStateMixin`] = ['15.0.0'];
deprecated['ReactPerf.printDOM'] = ['15.0.0', 'ReactPerf.printOperations'];
deprecated['Perf.printDOM'] = ['15.0.0', 'Perf.printOperations'];
deprecated['ReactPerf.getMeasurementsSummaryMap'] = ['15.0.0', 'ReactPerf.getWasted'];
deprecated['Perf.getMeasurementsSummaryMap'] = ['15.0.0', 'Perf.getWasted'];
// 15.5.0
deprecated[`${pragma}.createClass`] = ['15.5.0', 'the npm module create-react-class'];
deprecated[`${pragma}.PropTypes`] = ['15.5.0', 'the npm module prop-types'];
return deprecated;
}
function isDeprecated(method) {
var deprecated = getDeprecated();
return (
deprecated &&
deprecated[method] &&
versionUtil.test(context, deprecated[method][0])
);
}
function checkDeprecation(node, method) {
if (!isDeprecated(method)) {
return;
}
var deprecated = getDeprecated();
context.report({
node: node,
message: DEPRECATED_MESSAGE,
data: {
oldMethod: method,
version: deprecated[method][0],
newMethod: deprecated[method][1] ? `, use ${deprecated[method][1]} instead` : ''
}
});
}
function getReactModuleName(node) {
var moduleName = false;
if (!node.init) {
return moduleName;
}
for (var module in MODULES) {
if (!has(MODULES, module)) {
continue;
}
moduleName = MODULES[module].find(function(name) {
return name === node.init.name;
});
if (moduleName) {
break;
}
}
return moduleName;
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
MemberExpression: function(node) {
checkDeprecation(node, sourceCode.getText(node));
},
ImportDeclaration: function(node) {
var isReactImport = typeof MODULES[node.source.value] !== 'undefined';
if (!isReactImport) {
return;
}
node.specifiers.forEach(function(specifier) {
if (!specifier.imported) {
return;
}
checkDeprecation(node, `${MODULES[node.source.value][0]}.${specifier.imported.name}`);
});
},
VariableDeclarator: function(node) {
var reactModuleName = getReactModuleName(node);
var isRequire = node.init && node.init.callee && node.init.callee.name === 'require';
var isReactRequire =
node.init && node.init.arguments &&
node.init.arguments.length && typeof MODULES[node.init.arguments[0].value] !== 'undefined'
;
var isDestructuring = node.id && node.id.type === 'ObjectPattern';
if (
!(isDestructuring && reactModuleName) &&
!(isDestructuring && isRequire && isReactRequire)
) {
return;
}
node.id.properties.forEach(function(property) {
checkDeprecation(node, `${reactModuleName || pragma}.${property.key.name}`);
});
}
};
}
};

View File

@@ -0,0 +1,9 @@
/**
* @fileoverview Prevent usage of setState in componentDidMount
* @author Yannick Croissant
*/
'use strict';
var makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule');
module.exports = makeNoMethodSetStateRule('componentDidMount');

View File

@@ -0,0 +1,9 @@
/**
* @fileoverview Prevent usage of setState in componentDidUpdate
* @author Yannick Croissant
*/
'use strict';
var makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule');
module.exports = makeNoMethodSetStateRule('componentDidUpdate');

View File

@@ -0,0 +1,89 @@
/**
* @fileoverview Prevent direct mutation of this.state
* @author David Petersen
*/
'use strict';
var has = require('has');
var Components = require('../util/Components');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent direct mutation of this.state',
category: 'Possible Errors',
recommended: true
}
},
create: Components.detect(function(context, components, utils) {
/**
* Checks if the component is valid
* @param {Object} component The component to process
* @returns {Boolean} True if the component is valid, false if not.
*/
function isValid(component) {
return Boolean(component && !component.mutateSetState);
}
/**
* Reports undeclared proptypes for a given component
* @param {Object} component The component to process
*/
function reportMutations(component) {
var mutation;
for (var i = 0, j = component.mutations.length; i < j; i++) {
mutation = component.mutations[i];
context.report({
node: mutation,
message: 'Do not mutate state directly. Use setState().'
});
}
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
AssignmentExpression: function(node) {
var item;
if (!node.left || !node.left.object || !node.left.object.object) {
return;
}
item = node.left.object;
while (item.object.property) {
item = item.object;
}
if (
item.object.type === 'ThisExpression' &&
item.property.name === 'state'
) {
var component = components.get(utils.getParentComponent());
var mutations = component && component.mutations || [];
mutations.push(node.left.object);
components.set(node, {
mutateSetState: true,
mutations: mutations
});
}
},
'Program:exit': function() {
var list = components.list();
for (var component in list) {
if (!has(list, component) || isValid(list[component])) {
continue;
}
reportMutations(list[component]);
}
}
};
})
};

View File

@@ -0,0 +1,49 @@
/**
* @fileoverview Prevent usage of findDOMNode
* @author Yannick Croissant
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent usage of findDOMNode',
category: 'Best Practices',
recommended: true
},
schema: []
},
create: function(context) {
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
CallExpression: function(node) {
var callee = node.callee;
var isfindDOMNode =
(callee.object && callee.object.callee && callee.object.callee.name === 'findDOMNode') ||
(callee.property && callee.property.name === 'findDOMNode')
;
if (!isfindDOMNode) {
return;
}
context.report({
node: callee,
message: 'Do not use findDOMNode'
});
}
};
}
};

View File

@@ -0,0 +1,51 @@
/**
* @fileoverview Prevent usage of isMounted
* @author Joe Lencioni
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent usage of isMounted',
category: 'Best Practices',
recommended: true
},
schema: []
},
create: function(context) {
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
CallExpression: function(node) {
var callee = node.callee;
if (callee.type !== 'MemberExpression') {
return;
}
if (callee.object.type !== 'ThisExpression' || callee.property.name !== 'isMounted') {
return;
}
var ancestors = context.getAncestors(callee);
for (var i = 0, j = ancestors.length; i < j; i++) {
if (ancestors[i].type === 'Property' || ancestors[i].type === 'MethodDefinition') {
context.report({
node: callee,
message: 'Do not use isMounted'
});
break;
}
}
}
};
}
};

View File

@@ -0,0 +1,75 @@
/**
* @fileoverview Prevent multiple component definition per file
* @author Yannick Croissant
*/
'use strict';
var has = require('has');
var Components = require('../util/Components');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent multiple component definition per file',
category: 'Stylistic Issues',
recommended: false
},
schema: [{
type: 'object',
properties: {
ignoreStateless: {
default: false,
type: 'boolean'
}
},
additionalProperties: false
}]
},
create: Components.detect(function(context, components) {
var configuration = context.options[0] || {};
var ignoreStateless = configuration.ignoreStateless || false;
var MULTI_COMP_MESSAGE = 'Declare only one React component per file';
/**
* Checks if the component is ignored
* @param {Object} component The component being checked.
* @returns {Boolean} True if the component is ignored, false if not.
*/
function isIgnored(component) {
return ignoreStateless && /Function/.test(component.node.type);
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
'Program:exit': function() {
if (components.length() <= 1) {
return;
}
var list = components.list();
var i = 0;
for (var component in list) {
if (!has(list, component) || isIgnored(list[component]) || ++i === 1) {
continue;
}
context.report({
node: list[component].node,
message: MULTI_COMP_MESSAGE
});
}
}
};
})
};

View File

@@ -0,0 +1,70 @@
/**
* @fileoverview Prevent usage of the return value of React.render
* @author Dustan Kasten
*/
'use strict';
var versionUtil = require('../util/version');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent usage of the return value of React.render',
category: 'Best Practices',
recommended: true
},
schema: []
},
create: function(context) {
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
CallExpression: function(node) {
var callee = node.callee;
var parent = node.parent;
if (callee.type !== 'MemberExpression') {
return;
}
var calleeObjectName = /^ReactDOM$/;
if (versionUtil.test(context, '15.0.0')) {
calleeObjectName = /^ReactDOM$/;
} else if (versionUtil.test(context, '0.14.0')) {
calleeObjectName = /^React(DOM)?$/;
} else if (versionUtil.test(context, '0.13.0')) {
calleeObjectName = /^React$/;
}
if (
callee.object.type !== 'Identifier' ||
!calleeObjectName.test(callee.object.name) ||
callee.property.name !== 'render'
) {
return;
}
if (
parent.type === 'VariableDeclarator' ||
parent.type === 'Property' ||
parent.type === 'ReturnStatement' ||
parent.type === 'ArrowFunctionExpression'
) {
context.report({
node: callee,
message: `Do not depend on the return value from ${callee.object.name}.render`
});
}
}
};
}
};

View File

@@ -0,0 +1,85 @@
/**
* @fileoverview Prevent usage of setState
* @author Mark Dalgleish
*/
'use strict';
var has = require('has');
var Components = require('../util/Components');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent usage of setState',
category: 'Stylistic Issues',
recommended: false
},
schema: []
},
create: Components.detect(function(context, components, utils) {
/**
* Checks if the component is valid
* @param {Object} component The component to process
* @returns {Boolean} True if the component is valid, false if not.
*/
function isValid(component) {
return Boolean(component && !component.useSetState);
}
/**
* Reports usages of setState for a given component
* @param {Object} component The component to process
*/
function reportSetStateUsages(component) {
var setStateUsage;
for (var i = 0, j = component.setStateUsages.length; i < j; i++) {
setStateUsage = component.setStateUsages[i];
context.report({
node: setStateUsage,
message: 'Do not use setState'
});
}
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
CallExpression: function(node) {
var callee = node.callee;
if (
callee.type !== 'MemberExpression' ||
callee.object.type !== 'ThisExpression' ||
callee.property.name !== 'setState'
) {
return;
}
var component = components.get(utils.getParentComponent());
var setStateUsages = component && component.setStateUsages || [];
setStateUsages.push(callee);
components.set(node, {
useSetState: true,
setStateUsages: setStateUsages
});
},
'Program:exit': function() {
var list = components.list();
for (var component in list) {
if (!has(list, component) || isValid(list[component])) {
continue;
}
reportSetStateUsages(list[component]);
}
}
};
})
};

View File

@@ -0,0 +1,103 @@
/**
* @fileoverview Prevent string definitions for references and prevent referencing this.refs
* @author Tom Hastjarjanto
*/
'use strict';
var Components = require('../util/Components');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent string definitions for references and prevent referencing this.refs',
category: 'Best Practices',
recommended: true
},
schema: []
},
create: Components.detect(function(context, components, utils) {
/**
* Checks if we are using refs
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if we are using refs, false if not.
*/
function isRefsUsage(node) {
return Boolean(
(
utils.getParentES6Component() ||
utils.getParentES5Component()
) &&
node.object.type === 'ThisExpression' &&
node.property.name === 'refs'
);
}
/**
* Checks if we are using a ref attribute
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if we are using a ref attribute, false if not.
*/
function isRefAttribute(node) {
return Boolean(
node.type === 'JSXAttribute' &&
node.name &&
node.name.name === 'ref'
);
}
/**
* Checks if a node contains a string value
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if the node contains a string value, false if not.
*/
function containsStringLiteral(node) {
return Boolean(
node.value &&
node.value.type === 'Literal' &&
typeof node.value.value === 'string'
);
}
/**
* Checks if a node contains a string value within a jsx expression
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if the node contains a string value within a jsx expression, false if not.
*/
function containsStringExpressionContainer(node) {
return Boolean(
node.value &&
node.value.type === 'JSXExpressionContainer' &&
node.value.expression &&
node.value.expression.type === 'Literal' &&
typeof node.value.expression.value === 'string'
);
}
return {
MemberExpression: function(node) {
if (isRefsUsage(node)) {
context.report({
node: node,
message: 'Using this.refs is deprecated.'
});
}
},
JSXAttribute: function(node) {
if (
isRefAttribute(node) &&
(containsStringLiteral(node) || containsStringExpressionContainer(node))
) {
context.report({
node: node,
message: 'Using string literals in ref attributes is deprecated.'
});
}
}
};
})
};

View File

@@ -0,0 +1,80 @@
/**
* @fileoverview HTML special characters should be escaped.
* @author Patrick Hayes
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
// NOTE: '<' and '{' are also problematic characters, but they do not need
// to be included here because it is a syntax error when these characters are
// included accidentally.
var DEFAULTS = ['>', '"', '\'', '}'];
module.exports = {
meta: {
docs: {
description: 'Detect unescaped HTML entities, which might represent malformed tags',
category: 'Possible Errors',
recommended: true
},
schema: [{
type: 'object',
properties: {
forbid: {
type: 'array',
items: {
type: 'string'
}
}
},
additionalProperties: false
}]
},
create: function(context) {
function isInvalidEntity(node) {
var configuration = context.options[0] || {};
var entities = configuration.forbid || DEFAULTS;
// HTML entites are already escaped in node.value (as well as node.raw),
// so pull the raw text from context.getSourceCode()
for (var i = node.loc.start.line; i <= node.loc.end.line; i++) {
var rawLine = context.getSourceCode().lines[i - 1];
var start = 0;
var end = rawLine.length;
if (i === node.loc.start.line) {
start = node.loc.start.column;
}
if (i === node.loc.end.line) {
end = node.loc.end.column;
}
rawLine = rawLine.substring(start, end);
for (var j = 0; j < entities.length; j++) {
for (var index = 0; index < rawLine.length; index++) {
var c = rawLine[index];
if (c === entities[j]) {
context.report({
loc: {line: i, column: start + index},
message: 'HTML entities must be escaped.',
node: node
});
}
}
}
}
}
return {
Literal: function(node) {
if (node.type === 'Literal' && node.parent.type === 'JSXElement') {
if (isInvalidEntity(node)) {
context.report(node, 'HTML entities must be escaped.');
}
}
}
};
}
};

View File

@@ -0,0 +1,231 @@
/**
* @fileoverview Prevent usage of unknown DOM property
* @author Yannick Croissant
*/
'use strict';
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
var DEFAULTS = {
ignore: []
};
var UNKNOWN_MESSAGE = 'Unknown property \'{{name}}\' found, use \'{{standardName}}\' instead';
var DOM_ATTRIBUTE_NAMES = {
'accept-charset': 'acceptCharset',
class: 'className',
for: 'htmlFor',
'http-equiv': 'httpEquiv'
};
var SVGDOM_ATTRIBUTE_NAMES = {
'accent-height': 'accentHeight',
'alignment-baseline': 'alignmentBaseline',
'arabic-form': 'arabicForm',
'baseline-shift': 'baselineShift',
'cap-height': 'capHeight',
'clip-path': 'clipPath',
'clip-rule': 'clipRule',
'color-interpolation': 'colorInterpolation',
'color-interpolation-filters': 'colorInterpolationFilters',
'color-profile': 'colorProfile',
'color-rendering': 'colorRendering',
'dominant-baseline': 'dominantBaseline',
'enable-background': 'enableBackground',
'fill-opacity': 'fillOpacity',
'fill-rule': 'fillRule',
'flood-color': 'floodColor',
'flood-opacity': 'floodOpacity',
'font-family': 'fontFamily',
'font-size': 'fontSize',
'font-size-adjust': 'fontSizeAdjust',
'font-stretch': 'fontStretch',
'font-style': 'fontStyle',
'font-variant': 'fontVariant',
'font-weight': 'fontWeight',
'glyph-name': 'glyphName',
'glyph-orientation-horizontal': 'glyphOrientationHorizontal',
'glyph-orientation-vertical': 'glyphOrientationVertical',
'horiz-adv-x': 'horizAdvX',
'horiz-origin-x': 'horizOriginX',
'image-rendering': 'imageRendering',
'letter-spacing': 'letterSpacing',
'lighting-color': 'lightingColor',
'marker-end': 'markerEnd',
'marker-mid': 'markerMid',
'marker-start': 'markerStart',
'overline-position': 'overlinePosition',
'overline-thickness': 'overlineThickness',
'paint-order': 'paintOrder',
'panose-1': 'panose1',
'pointer-events': 'pointerEvents',
'rendering-intent': 'renderingIntent',
'shape-rendering': 'shapeRendering',
'stop-color': 'stopColor',
'stop-opacity': 'stopOpacity',
'strikethrough-position': 'strikethroughPosition',
'strikethrough-thickness': 'strikethroughThickness',
'stroke-dasharray': 'strokeDasharray',
'stroke-dashoffset': 'strokeDashoffset',
'stroke-linecap': 'strokeLinecap',
'stroke-linejoin': 'strokeLinejoin',
'stroke-miterlimit': 'strokeMiterlimit',
'stroke-opacity': 'strokeOpacity',
'stroke-width': 'strokeWidth',
'text-anchor': 'textAnchor',
'text-decoration': 'textDecoration',
'text-rendering': 'textRendering',
'underline-position': 'underlinePosition',
'underline-thickness': 'underlineThickness',
'unicode-bidi': 'unicodeBidi',
'unicode-range': 'unicodeRange',
'units-per-em': 'unitsPerEm',
'v-alphabetic': 'vAlphabetic',
'v-hanging': 'vHanging',
'v-ideographic': 'vIdeographic',
'v-mathematical': 'vMathematical',
'vector-effect': 'vectorEffect',
'vert-adv-y': 'vertAdvY',
'vert-origin-x': 'vertOriginX',
'vert-origin-y': 'vertOriginY',
'word-spacing': 'wordSpacing',
'writing-mode': 'writingMode',
'x-height': 'xHeight',
'xlink:actuate': 'xlinkActuate',
'xlink:arcrole': 'xlinkArcrole',
'xlink:href': 'xlinkHref',
'xlink:role': 'xlinkRole',
'xlink:show': 'xlinkShow',
'xlink:title': 'xlinkTitle',
'xlink:type': 'xlinkType',
'xml:base': 'xmlBase',
'xml:lang': 'xmlLang',
'xml:space': 'xmlSpace'
};
var DOM_PROPERTY_NAMES = [
// Standard
'acceptCharset', 'accessKey', 'allowFullScreen', 'allowTransparency', 'autoComplete', 'autoFocus', 'autoPlay',
'cellPadding', 'cellSpacing', 'charSet', 'classID', 'className', 'colSpan', 'contentEditable', 'contextMenu',
'crossOrigin', 'dateTime', 'encType', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', 'formTarget',
'frameBorder', 'hrefLang', 'htmlFor', 'httpEquiv', 'inputMode', 'keyParams', 'keyType', 'marginHeight', 'marginWidth',
'maxLength', 'mediaGroup', 'minLength', 'noValidate', 'onAnimationEnd', 'onAnimationIteration', 'onAnimationStart',
'onBlur', 'onChange', 'onClick', 'onContextMenu', 'onCopy', 'onCompositionEnd', 'onCompositionStart',
'onCompositionUpdate', 'onCut', 'onDoubleClick', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 'onDragLeave',
'onError', 'onFocus', 'onInput', 'onKeyDown', 'onKeyPress', 'onKeyUp', 'onLoad', 'onWheel', 'onDragOver',
'onDragStart', 'onDrop', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 'onMouseMove', 'onMouseOut', 'onMouseOver',
'onMouseUp', 'onPaste', 'onScroll', 'onSelect', 'onSubmit', 'onTransitionEnd', 'radioGroup', 'readOnly', 'rowSpan',
'spellCheck', 'srcDoc', 'srcLang', 'srcSet', 'tabIndex', 'useMap',
// Non standard
'autoCapitalize', 'autoCorrect',
'autoSave',
'itemProp', 'itemScope', 'itemType', 'itemRef', 'itemID'
];
// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------
/**
* Checks if a node matches the JSX tag convention.
* @param {Object} node - JSX element being tested.
* @returns {boolean} Whether or not the node name match the JSX tag convention.
*/
var tagConvention = /^[a-z][^-]*$/;
function isTagName(node) {
if (tagConvention.test(node.parent.name.name)) {
// http://www.w3.org/TR/custom-elements/#type-extension-semantics
return !node.parent.attributes.some(function(attrNode) {
return (
attrNode.type === 'JSXAttribute' &&
attrNode.name.type === 'JSXIdentifier' &&
attrNode.name.name === 'is'
);
});
}
return false;
}
/**
* Get the standard name of the attribute.
* @param {String} name - Name of the attribute.
* @returns {String} The standard name of the attribute.
*/
function getStandardName(name) {
if (DOM_ATTRIBUTE_NAMES[name]) {
return DOM_ATTRIBUTE_NAMES[name];
}
if (SVGDOM_ATTRIBUTE_NAMES[name]) {
return SVGDOM_ATTRIBUTE_NAMES[name];
}
var i;
var found = DOM_PROPERTY_NAMES.some(function(element, index) {
i = index;
return element.toLowerCase() === name;
});
return found ? DOM_PROPERTY_NAMES[i] : null;
}
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent usage of unknown DOM property',
category: 'Possible Errors',
recommended: true
},
fixable: 'code',
schema: [{
type: 'object',
properties: {
ignore: {
type: 'array',
items: {
type: 'string'
}
}
},
additionalProperties: false
}]
},
create: function(context) {
function getIgnoreConfig() {
return context.options[0] && context.options[0].ignore || DEFAULTS.ignore;
}
var sourceCode = context.getSourceCode();
return {
JSXAttribute: function(node) {
var ignoreNames = getIgnoreConfig();
var name = sourceCode.getText(node.name);
var standardName = getStandardName(name);
if (!isTagName(node) || !standardName || ignoreNames.indexOf(name) >= 0) {
return;
}
context.report({
node: node,
message: UNKNOWN_MESSAGE,
data: {
name: name,
standardName: standardName
},
fix: function(fixer) {
return fixer.replaceText(node.name, standardName);
}
});
}
};
}
};

View File

@@ -0,0 +1,957 @@
/**
* @fileoverview Prevent definitions of unused prop types
* @author Evgueni Naverniouk
*/
'use strict';
// As for exceptions for props.children or props.className (and alike) look at
// https://github.com/yannickcr/eslint-plugin-react/issues/7
var has = require('has');
var Components = require('../util/Components');
var variable = require('../util/variable');
var annotations = require('../util/annotations');
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
var DIRECT_PROPS_REGEX = /^props\s*(\.|\[)/;
var DIRECT_NEXT_PROPS_REGEX = /^nextProps\s*(\.|\[)/;
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent definitions of unused prop types',
category: 'Best Practices',
recommended: false
},
schema: [{
type: 'object',
properties: {
customValidators: {
type: 'array',
items: {
type: 'string'
}
},
skipShapeProps: {
type: 'boolean'
}
},
additionalProperties: false
}]
},
create: Components.detect(function(context, components, utils) {
var defaults = {skipShapeProps: true};
var sourceCode = context.getSourceCode();
var configuration = Object.assign({}, defaults, context.options[0] || {});
var skipShapeProps = configuration.skipShapeProps;
var customValidators = configuration.customValidators || [];
// Used to track the type annotations in scope.
// Necessary because babel's scopes do not track type annotations.
var stack = null;
var UNUSED_MESSAGE = '\'{{name}}\' PropType is defined but prop is never used';
/**
* Helper for accessing the current scope in the stack.
* @param {string} key The name of the identifier to access. If omitted, returns the full scope.
* @param {ASTNode} value If provided sets the new value for the identifier.
* @returns {Object|ASTNode} Either the whole scope or the ASTNode associated with the given identifier.
*/
function typeScope(key, value) {
if (arguments.length === 0) {
return stack[stack.length - 1];
} else if (arguments.length === 1) {
return stack[stack.length - 1][key];
}
stack[stack.length - 1][key] = value;
return value;
}
/**
* Check if we are in a class constructor
* @return {boolean} true if we are in a class constructor, false if not
**/
function inComponentWillReceiveProps() {
var scope = context.getScope();
while (scope) {
if (
scope.block && scope.block.parent &&
scope.block.parent.key && scope.block.parent.key.name === 'componentWillReceiveProps'
) {
return true;
}
scope = scope.upper;
}
return false;
}
/**
* Checks if we are using a prop
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if we are using a prop, false if not.
*/
function isPropTypesUsage(node) {
var isClassUsage = (
(utils.getParentES6Component() || utils.getParentES5Component()) &&
node.object.type === 'ThisExpression' && node.property.name === 'props'
);
var isStatelessFunctionUsage = node.object.name === 'props';
var isNextPropsUsage = node.object.name === 'nextProps' && inComponentWillReceiveProps();
return isClassUsage || isStatelessFunctionUsage || isNextPropsUsage;
}
/**
* Checks if we are declaring a `props` class property with a flow type annotation.
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if the node is a type annotated props declaration, false if not.
*/
function isAnnotatedClassPropsDeclaration(node) {
if (node && node.type === 'ClassProperty') {
var tokens = context.getFirstTokens(node, 2);
if (
node.typeAnnotation && (
tokens[0].value === 'props' ||
(tokens[1] && tokens[1].value === 'props')
)
) {
return true;
}
}
return false;
}
/**
* Checks if we are declaring a prop
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if we are declaring a prop, false if not.
*/
function isPropTypesDeclaration(node) {
// Special case for class properties
// (babel-eslint does not expose property name so we have to rely on tokens)
if (node && node.type === 'ClassProperty') {
var tokens = context.getFirstTokens(node, 2);
if (
tokens[0].value === 'propTypes' ||
(tokens[1] && tokens[1].value === 'propTypes')
) {
return true;
}
return false;
}
return Boolean(
node &&
node.name === 'propTypes'
);
}
/**
* Checks if prop should be validated by plugin-react-proptypes
* @param {String} validator Name of validator to check.
* @returns {Boolean} True if validator should be checked by custom validator.
*/
function hasCustomValidator(validator) {
return customValidators.indexOf(validator) !== -1;
}
/**
* Checks if the component must be validated
* @param {Object} component The component to process
* @returns {Boolean} True if the component must be validated, false if not.
*/
function mustBeValidated(component) {
return Boolean(
component &&
component.usedPropTypes &&
!component.ignorePropsValidation
);
}
/**
* Returns true if the given node is a React Component lifecycle method
* @param {ASTNode} node The AST node being checked.
* @return {Boolean} True if the node is a lifecycle method
*/
function isNodeALifeCycleMethod(node) {
var nodeKeyName = (node.key || {}).name;
return (
node.kind === 'constructor' ||
nodeKeyName === 'componentWillReceiveProps' ||
nodeKeyName === 'shouldComponentUpdate' ||
nodeKeyName === 'componentWillUpdate' ||
nodeKeyName === 'componentDidUpdate'
);
}
/**
* Returns true if the given node is inside a React Component lifecycle
* method.
* @param {ASTNode} node The AST node being checked.
* @return {Boolean} True if the node is inside a lifecycle method
*/
function isInLifeCycleMethod(node) {
if (node.type === 'MethodDefinition' && isNodeALifeCycleMethod(node)) {
return true;
}
if (node.parent) {
return isInLifeCycleMethod(node.parent);
}
return false;
}
/**
* Checks if a prop init name matches common naming patterns
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if the prop name matches
*/
function isPropAttributeName (node) {
return (
node.init.name === 'props' ||
node.init.name === 'nextProps' ||
node.init.name === 'prevProps'
);
}
/**
* Checks if a prop is used
* @param {ASTNode} node The AST node being checked.
* @param {Object} prop Declared prop object
* @returns {Boolean} True if the prop is used, false if not.
*/
function isPropUsed(node, prop) {
for (var i = 0, l = node.usedPropTypes.length; i < l; i++) {
var usedProp = node.usedPropTypes[i];
if (
prop.type === 'shape' ||
prop.name === '__ANY_KEY__' ||
usedProp.name === prop.name
) {
return true;
}
}
return false;
}
/**
* Checks if the prop has spread operator.
* @param {ASTNode} node The AST node being marked.
* @returns {Boolean} True if the prop has spread operator, false if not.
*/
function hasSpreadOperator(node) {
var tokens = sourceCode.getTokens(node);
return tokens.length && tokens[0].value === '...';
}
/**
* Retrieve the name of a key node
* @param {ASTNode} node The AST node with the key.
* @return {string} the name of the key
*/
function getKeyValue(node) {
if (node.type === 'ObjectTypeProperty') {
var tokens = context.getFirstTokens(node, 1);
return tokens[0].value;
}
var key = node.key || node.argument;
return key.type === 'Identifier' ? key.name : key.value;
}
/**
* Iterates through a properties node, like a customized forEach.
* @param {Object[]} properties Array of properties to iterate.
* @param {Function} fn Function to call on each property, receives property key
and property value. (key, value) => void
*/
function iterateProperties(properties, fn) {
if (properties.length && typeof fn === 'function') {
for (var i = 0, j = properties.length; i < j; i++) {
var node = properties[i];
var key = getKeyValue(node);
var value = node.value;
fn(key, value);
}
}
}
/**
* Creates the representation of the React propTypes for the component.
* The representation is used to verify nested used properties.
* @param {ASTNode} value Node of the PropTypes for the desired property
* @param {String} parentName Name of the parent prop node.
* @return {Object|Boolean} The representation of the declaration, true means
* the property is declared without the need for further analysis.
*/
function buildReactDeclarationTypes(value, parentName) {
if (
value &&
value.callee &&
value.callee.object &&
hasCustomValidator(value.callee.object.name)
) {
return true;
}
if (
value &&
value.type === 'MemberExpression' &&
value.property &&
value.property.name &&
value.property.name === 'isRequired'
) {
value = value.object;
}
// Verify PropTypes that are functions
if (
value &&
value.type === 'CallExpression' &&
value.callee &&
value.callee.property &&
value.callee.property.name &&
value.arguments &&
value.arguments.length > 0
) {
var callName = value.callee.property.name;
var argument = value.arguments[0];
switch (callName) {
case 'shape':
if (skipShapeProps) {
return true;
}
if (argument.type !== 'ObjectExpression') {
// Invalid proptype or cannot analyse statically
return true;
}
var shapeTypeDefinition = {
type: 'shape',
children: []
};
iterateProperties(argument.properties, function(childKey, childValue) {
var fullName = [parentName, childKey].join('.');
var types = buildReactDeclarationTypes(childValue, fullName);
if (types === true) {
types = {};
}
types.fullName = fullName;
types.name = childKey;
types.node = childValue;
shapeTypeDefinition.children.push(types);
});
return shapeTypeDefinition;
case 'arrayOf':
case 'objectOf':
var fullName = [parentName, '*'].join('.');
var child = buildReactDeclarationTypes(argument, fullName);
if (child === true) {
child = {};
}
child.fullName = fullName;
child.name = '__ANY_KEY__';
child.node = argument;
return {
type: 'object',
children: [child]
};
case 'oneOfType':
if (
!argument.elements ||
!argument.elements.length
) {
// Invalid proptype or cannot analyse statically
return true;
}
var unionTypeDefinition = {
type: 'union',
children: []
};
for (var i = 0, j = argument.elements.length; i < j; i++) {
var type = buildReactDeclarationTypes(argument.elements[i], parentName);
// keep only complex type
if (type !== true) {
if (type.children === true) {
// every child is accepted for one type, abort type analysis
unionTypeDefinition.children = true;
return unionTypeDefinition;
}
}
unionTypeDefinition.children.push(type);
}
if (unionTypeDefinition.length === 0) {
// no complex type found, simply accept everything
return true;
}
return unionTypeDefinition;
case 'instanceOf':
return {
type: 'instance',
// Accept all children because we can't know what type they are
children: true
};
case 'oneOf':
default:
return true;
}
}
// Unknown property or accepts everything (any, object, ...)
return true;
}
/**
* Creates the representation of the React props type annotation for the component.
* The representation is used to verify nested used properties.
* @param {ASTNode} annotation Type annotation for the props class property.
* @param {String} parentName Name of the parent prop node.
* @return {Object|Boolean} The representation of the declaration, true means
* the property is declared without the need for further analysis.
*/
function buildTypeAnnotationDeclarationTypes(annotation, parentName) {
switch (annotation.type) {
case 'GenericTypeAnnotation':
if (typeScope(annotation.id.name)) {
return buildTypeAnnotationDeclarationTypes(typeScope(annotation.id.name), parentName);
}
return true;
case 'ObjectTypeAnnotation':
var shapeTypeDefinition = {
type: 'shape',
children: []
};
iterateProperties(annotation.properties, function(childKey, childValue) {
var fullName = [parentName, childKey].join('.');
var types = buildTypeAnnotationDeclarationTypes(childValue, fullName);
if (types === true) {
types = {};
}
types.fullName = fullName;
types.name = childKey;
types.node = childValue;
shapeTypeDefinition.children.push(types);
});
return shapeTypeDefinition;
case 'UnionTypeAnnotation':
var unionTypeDefinition = {
type: 'union',
children: []
};
for (var i = 0, j = annotation.types.length; i < j; i++) {
var type = buildTypeAnnotationDeclarationTypes(annotation.types[i], parentName);
// keep only complex type
if (type !== true) {
if (type.children === true) {
// every child is accepted for one type, abort type analysis
unionTypeDefinition.children = true;
return unionTypeDefinition;
}
}
unionTypeDefinition.children.push(type);
}
if (unionTypeDefinition.children.length === 0) {
// no complex type found, simply accept everything
return true;
}
return unionTypeDefinition;
case 'ArrayTypeAnnotation':
var fullName = [parentName, '*'].join('.');
var child = buildTypeAnnotationDeclarationTypes(annotation.elementType, fullName);
if (child === true) {
child = {};
}
child.fullName = fullName;
child.name = '__ANY_KEY__';
child.node = annotation;
return {
type: 'object',
children: [child]
};
default:
// Unknown or accepts everything.
return true;
}
}
/**
* Check if we are in a class constructor
* @return {boolean} true if we are in a class constructor, false if not
*/
function inConstructor() {
var scope = context.getScope();
while (scope) {
if (scope.block && scope.block.parent && scope.block.parent.kind === 'constructor') {
return true;
}
scope = scope.upper;
}
return false;
}
/**
* Retrieve the name of a property node
* @param {ASTNode} node The AST node with the property.
* @return {string} the name of the property or undefined if not found
*/
function getPropertyName(node) {
var isDirectProp = DIRECT_PROPS_REGEX.test(sourceCode.getText(node));
var isDirectNextProp = DIRECT_NEXT_PROPS_REGEX.test(sourceCode.getText(node));
var isInClassComponent = utils.getParentES6Component() || utils.getParentES5Component();
var isNotInConstructor = !inConstructor(node);
var isNotInComponentWillReceiveProps = !inComponentWillReceiveProps();
if ((isDirectProp || isDirectNextProp)
&& isInClassComponent
&& isNotInConstructor
&& isNotInComponentWillReceiveProps) {
return void 0;
}
if (!isDirectProp && !isDirectNextProp) {
node = node.parent;
}
var property = node.property;
if (property) {
switch (property.type) {
case 'Identifier':
if (node.computed) {
return '__COMPUTED_PROP__';
}
return property.name;
case 'MemberExpression':
return void 0;
case 'Literal':
// Accept computed properties that are literal strings
if (typeof property.value === 'string') {
return property.value;
}
// falls through
default:
if (node.computed) {
return '__COMPUTED_PROP__';
}
break;
}
}
return void 0;
}
/**
* Mark a prop type as used
* @param {ASTNode} node The AST node being marked.
*/
function markPropTypesAsUsed(node, parentNames) {
parentNames = parentNames || [];
var type;
var name;
var allNames;
var properties;
switch (node.type) {
case 'MemberExpression':
name = getPropertyName(node);
if (name) {
allNames = parentNames.concat(name);
if (node.parent.type === 'MemberExpression') {
markPropTypesAsUsed(node.parent, allNames);
}
// Do not mark computed props as used.
type = name !== '__COMPUTED_PROP__' ? 'direct' : null;
} else if (
node.parent.id &&
node.parent.id.properties &&
node.parent.id.properties.length &&
getKeyValue(node.parent.id.properties[0])
) {
type = 'destructuring';
properties = node.parent.id.properties;
}
break;
case 'ArrowFunctionExpression':
case 'FunctionDeclaration':
case 'FunctionExpression':
type = 'destructuring';
properties = node.params[0].properties;
break;
case 'VariableDeclarator':
for (var i = 0, j = node.id.properties.length; i < j; i++) {
// let {props: {firstname}} = this
var thisDestructuring = (
node.id.properties[i].key && (
(node.id.properties[i].key.name === 'props' || node.id.properties[i].key.value === 'props') &&
node.id.properties[i].value.type === 'ObjectPattern'
)
);
// let {firstname} = props
var genericDestructuring = isPropAttributeName(node) && (
utils.getParentStatelessComponent() ||
isInLifeCycleMethod(node)
);
if (thisDestructuring) {
properties = node.id.properties[i].value.properties;
} else if (genericDestructuring) {
properties = node.id.properties;
} else {
continue;
}
type = 'destructuring';
break;
}
break;
default:
throw new Error(`${node.type} ASTNodes are not handled by markPropTypesAsUsed`);
}
var component = components.get(utils.getParentComponent());
var usedPropTypes = component && component.usedPropTypes || [];
var ignorePropsValidation = component && component.ignorePropsValidation || false;
switch (type) {
case 'direct':
// Ignore Object methods
if (Object.prototype[name]) {
break;
}
usedPropTypes.push({
name: name,
allNames: allNames
});
break;
case 'destructuring':
for (var k = 0, l = (properties || []).length; k < l; k++) {
if (hasSpreadOperator(properties[k]) || properties[k].computed) {
ignorePropsValidation = true;
break;
}
var propName = getKeyValue(properties[k]);
var currentNode = node;
allNames = [];
while (currentNode.property && currentNode.property.name !== 'props') {
allNames.unshift(currentNode.property.name);
currentNode = currentNode.object;
}
allNames.push(propName);
if (propName) {
usedPropTypes.push({
allNames: allNames,
name: propName
});
}
}
break;
default:
break;
}
components.set(node, {
usedPropTypes: usedPropTypes,
ignorePropsValidation: ignorePropsValidation
});
}
/**
* Mark a prop type as declared
* @param {ASTNode} node The AST node being checked.
* @param {propTypes} node The AST node containing the proptypes
*/
function markPropTypesAsDeclared(node, propTypes) {
var component = components.get(node);
var declaredPropTypes = component && component.declaredPropTypes || [];
var ignorePropsValidation = component && component.ignorePropsValidation || false;
switch (propTypes && propTypes.type) {
case 'ObjectTypeAnnotation':
iterateProperties(propTypes.properties, function(key, value) {
if (!value) {
ignorePropsValidation = true;
return;
}
var types = buildTypeAnnotationDeclarationTypes(value, key);
if (types === true) {
types = {};
}
types.fullName = key;
types.name = key;
types.node = value;
declaredPropTypes.push(types);
});
break;
case 'ObjectExpression':
iterateProperties(propTypes.properties, function(key, value) {
if (!value) {
ignorePropsValidation = true;
return;
}
var types = buildReactDeclarationTypes(value, key);
if (types === true) {
types = {};
}
types.fullName = key;
types.name = key;
types.node = value;
declaredPropTypes.push(types);
});
break;
case 'MemberExpression':
break;
case 'Identifier':
var variablesInScope = variable.variablesInScope(context);
for (var i = 0, j = variablesInScope.length; i < j; i++) {
if (variablesInScope[i].name !== propTypes.name) {
continue;
}
var defInScope = variablesInScope[i].defs[variablesInScope[i].defs.length - 1];
markPropTypesAsDeclared(node, defInScope.node && defInScope.node.init);
return;
}
ignorePropsValidation = true;
break;
case null:
break;
default:
ignorePropsValidation = true;
break;
}
components.set(node, {
declaredPropTypes: declaredPropTypes,
ignorePropsValidation: ignorePropsValidation
});
}
/**
* Used to recursively loop through each declared prop type
* @param {Object} component The component to process
* @param {Array} props List of props to validate
*/
function reportUnusedPropType (component, props) {
// Skip props that check instances
if (props === true) {
return;
}
(props || []).forEach(function (prop) {
// Skip props that check instances
if (prop === true) {
return;
}
if (prop.node && !isPropUsed(component, prop)) {
context.report(
prop.node,
UNUSED_MESSAGE, {
name: prop.fullName
}
);
}
if (prop.children) {
reportUnusedPropType(component, prop.children);
}
});
}
/**
* Reports unused proptypes for a given component
* @param {Object} component The component to process
*/
function reportUnusedPropTypes(component) {
reportUnusedPropType(component, component.declaredPropTypes);
}
/**
* Resolve the type annotation for a given node.
* Flow annotations are sometimes wrapped in outer `TypeAnnotation`
* and `NullableTypeAnnotation` nodes which obscure the annotation we're
* interested in.
* This method also resolves type aliases where possible.
*
* @param {ASTNode} node The annotation or a node containing the type annotation.
* @returns {ASTNode} The resolved type annotation for the node.
*/
function resolveTypeAnnotation(node) {
var annotation = node.typeAnnotation || node;
while (annotation && (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation')) {
annotation = annotation.typeAnnotation;
}
if (annotation.type === 'GenericTypeAnnotation' && typeScope(annotation.id.name)) {
return typeScope(annotation.id.name);
}
return annotation;
}
/**
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
* FunctionDeclaration, or FunctionExpression
*/
function markDestructuredFunctionArgumentsAsUsed(node) {
var destructuring = node.params && node.params[0] && node.params[0].type === 'ObjectPattern';
if (destructuring && components.get(node)) {
markPropTypesAsUsed(node);
}
}
/**
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
* FunctionDeclaration, or FunctionExpression
*/
function markAnnotatedFunctionArgumentsAsDeclared(node) {
if (!node.params || !node.params.length || !annotations.isAnnotatedFunctionPropsDeclaration(node, context)) {
return;
}
markPropTypesAsDeclared(node, resolveTypeAnnotation(node.params[0]));
}
/**
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
* FunctionDeclaration, or FunctionExpression
*/
function handleStatelessComponent(node) {
markDestructuredFunctionArgumentsAsUsed(node);
markAnnotatedFunctionArgumentsAsDeclared(node);
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
ClassProperty: function(node) {
if (isAnnotatedClassPropsDeclaration(node)) {
markPropTypesAsDeclared(node, resolveTypeAnnotation(node));
} else if (isPropTypesDeclaration(node)) {
markPropTypesAsDeclared(node, node.value);
}
},
VariableDeclarator: function(node) {
var destructuring = node.init && node.id && node.id.type === 'ObjectPattern';
// let {props: {firstname}} = this
var thisDestructuring = destructuring && node.init.type === 'ThisExpression';
// let {firstname} = props
var statelessDestructuring = destructuring && isPropAttributeName(node) && (
utils.getParentStatelessComponent() ||
isInLifeCycleMethod(node)
);
if (!thisDestructuring && !statelessDestructuring) {
return;
}
markPropTypesAsUsed(node);
},
FunctionDeclaration: handleStatelessComponent,
ArrowFunctionExpression: handleStatelessComponent,
FunctionExpression: handleStatelessComponent,
MemberExpression: function(node) {
var type;
if (isPropTypesUsage(node)) {
type = 'usage';
} else if (isPropTypesDeclaration(node.property)) {
type = 'declaration';
}
switch (type) {
case 'usage':
markPropTypesAsUsed(node);
break;
case 'declaration':
var component = utils.getRelatedComponent(node);
if (!component) {
return;
}
markPropTypesAsDeclared(component.node, node.parent.right || node.parent);
break;
default:
break;
}
},
MethodDefinition: function(node) {
if (!isPropTypesDeclaration(node.key)) {
return;
}
var i = node.value.body.body.length - 1;
for (; i >= 0; i--) {
if (node.value.body.body[i].type === 'ReturnStatement') {
break;
}
}
if (i >= 0) {
markPropTypesAsDeclared(node, node.value.body.body[i].argument);
}
},
ObjectPattern: function(node) {
// If the object pattern is a destructured props object in a lifecycle
// method -- mark it for used props.
if (isNodeALifeCycleMethod(node.parent.parent)) {
node.properties.forEach(function(property, i) {
if (i === 0) {
markPropTypesAsUsed(node.parent);
}
});
}
},
ObjectExpression: function(node) {
// Search for the proptypes declaration
node.properties.forEach(function(property) {
if (!isPropTypesDeclaration(property.key)) {
return;
}
markPropTypesAsDeclared(node, property.value);
});
},
TypeAlias: function(node) {
typeScope(node.id.name, node.right);
},
Program: function() {
stack = [{}];
},
BlockStatement: function () {
stack.push(Object.create(typeScope()));
},
'BlockStatement:exit': function () {
stack.pop();
},
'Program:exit': function() {
stack = null;
var list = components.list();
// Report undeclared proptypes for all classes
for (var component in list) {
if (!has(list, component) || !mustBeValidated(list[component])) {
continue;
}
reportUnusedPropTypes(list[component]);
}
}
};
})
};

View File

@@ -0,0 +1,9 @@
/**
* @fileoverview Prevent usage of setState in componentWillUpdate
* @author Yannick Croissant
*/
'use strict';
var makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule');
module.exports = makeNoMethodSetStateRule('componentWillUpdate');

View File

@@ -0,0 +1,48 @@
/**
* @fileoverview Enforce ES5 or ES6 class for React Components
* @author Dan Hamilton
*/
'use strict';
var Components = require('../util/Components');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Enforce ES5 or ES6 class for React Components',
category: 'Stylistic Issues',
recommended: false
},
schema: [{
enum: ['always', 'never']
}]
},
create: Components.detect(function(context, components, utils) {
var configuration = context.options[0] || 'always';
return {
ObjectExpression: function(node) {
if (utils.isES5Component(node) && configuration === 'always') {
context.report({
node: node,
message: 'Component should use es6 class instead of createClass'
});
}
},
ClassDeclaration: function(node) {
if (utils.isES6Component(node) && configuration === 'never') {
context.report({
node: node,
message: 'Component should use createClass instead of es6 class'
});
}
}
};
})
};

View File

@@ -0,0 +1,414 @@
/**
* @fileoverview Enforce stateless components to be written as a pure function
* @author Yannick Croissant
* @author Alberto Rodríguez
* @copyright 2015 Alberto Rodríguez. All rights reserved.
*/
'use strict';
var has = require('has');
var Components = require('../util/Components');
var versionUtil = require('../util/version');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Enforce stateless components to be written as a pure function',
category: 'Stylistic Issues',
recommended: false
},
schema: [{
type: 'object',
properties: {
ignorePureComponents: {
default: false,
type: 'boolean'
}
},
additionalProperties: false
}]
},
create: Components.detect(function(context, components, utils) {
var configuration = context.options[0] || {};
var ignorePureComponents = configuration.ignorePureComponents || false;
var sourceCode = context.getSourceCode();
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
/**
* Get properties name
* @param {Object} node - Property.
* @returns {String} Property name.
*/
function getPropertyName(node) {
// Special case for class properties
// (babel-eslint does not expose property name so we have to rely on tokens)
if (node.type === 'ClassProperty') {
var tokens = context.getFirstTokens(node, 2);
return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value;
}
return node.key.name;
}
/**
* Get properties for a given AST node
* @param {ASTNode} node The AST node being checked.
* @returns {Array} Properties array.
*/
function getComponentProperties(node) {
switch (node.type) {
case 'ClassExpression':
case 'ClassDeclaration':
return node.body.body;
case 'ObjectExpression':
return node.properties;
default:
return [];
}
}
/**
* Checks whether a given array of statements is a single call of `super`.
* @see ESLint no-useless-constructor rule
* @param {ASTNode[]} body - An array of statements to check.
* @returns {boolean} `true` if the body is a single call of `super`.
*/
function isSingleSuperCall(body) {
return (
body.length === 1 &&
body[0].type === 'ExpressionStatement' &&
body[0].expression.type === 'CallExpression' &&
body[0].expression.callee.type === 'Super'
);
}
/**
* Checks whether a given node is a pattern which doesn't have any side effects.
* Default parameters and Destructuring parameters can have side effects.
* @see ESLint no-useless-constructor rule
* @param {ASTNode} node - A pattern node.
* @returns {boolean} `true` if the node doesn't have any side effects.
*/
function isSimple(node) {
return node.type === 'Identifier' || node.type === 'RestElement';
}
/**
* Checks whether a given array of expressions is `...arguments` or not.
* `super(...arguments)` passes all arguments through.
* @see ESLint no-useless-constructor rule
* @param {ASTNode[]} superArgs - An array of expressions to check.
* @returns {boolean} `true` if the superArgs is `...arguments`.
*/
function isSpreadArguments(superArgs) {
return (
superArgs.length === 1 &&
superArgs[0].type === 'SpreadElement' &&
superArgs[0].argument.type === 'Identifier' &&
superArgs[0].argument.name === 'arguments'
);
}
/**
* Checks whether given 2 nodes are identifiers which have the same name or not.
* @see ESLint no-useless-constructor rule
* @param {ASTNode} ctorParam - A node to check.
* @param {ASTNode} superArg - A node to check.
* @returns {boolean} `true` if the nodes are identifiers which have the same
* name.
*/
function isValidIdentifierPair(ctorParam, superArg) {
return (
ctorParam.type === 'Identifier' &&
superArg.type === 'Identifier' &&
ctorParam.name === superArg.name
);
}
/**
* Checks whether given 2 nodes are a rest/spread pair which has the same values.
* @see ESLint no-useless-constructor rule
* @param {ASTNode} ctorParam - A node to check.
* @param {ASTNode} superArg - A node to check.
* @returns {boolean} `true` if the nodes are a rest/spread pair which has the
* same values.
*/
function isValidRestSpreadPair(ctorParam, superArg) {
return (
ctorParam.type === 'RestElement' &&
superArg.type === 'SpreadElement' &&
isValidIdentifierPair(ctorParam.argument, superArg.argument)
);
}
/**
* Checks whether given 2 nodes have the same value or not.
* @see ESLint no-useless-constructor rule
* @param {ASTNode} ctorParam - A node to check.
* @param {ASTNode} superArg - A node to check.
* @returns {boolean} `true` if the nodes have the same value or not.
*/
function isValidPair(ctorParam, superArg) {
return (
isValidIdentifierPair(ctorParam, superArg) ||
isValidRestSpreadPair(ctorParam, superArg)
);
}
/**
* Checks whether the parameters of a constructor and the arguments of `super()`
* have the same values or not.
* @see ESLint no-useless-constructor rule
* @param {ASTNode} ctorParams - The parameters of a constructor to check.
* @param {ASTNode} superArgs - The arguments of `super()` to check.
* @returns {boolean} `true` if those have the same values.
*/
function isPassingThrough(ctorParams, superArgs) {
if (ctorParams.length !== superArgs.length) {
return false;
}
for (var i = 0; i < ctorParams.length; ++i) {
if (!isValidPair(ctorParams[i], superArgs[i])) {
return false;
}
}
return true;
}
/**
* Checks whether the constructor body is a redundant super call.
* @see ESLint no-useless-constructor rule
* @param {Array} body - constructor body content.
* @param {Array} ctorParams - The params to check against super call.
* @returns {boolean} true if the construtor body is redundant
*/
function isRedundantSuperCall(body, ctorParams) {
return (
isSingleSuperCall(body) &&
ctorParams.every(isSimple) &&
(
isSpreadArguments(body[0].expression.arguments) ||
isPassingThrough(ctorParams, body[0].expression.arguments)
)
);
}
/**
* Check if a given AST node have any other properties the ones available in stateless components
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if the node has at least one other property, false if not.
*/
function hasOtherProperties(node) {
var properties = getComponentProperties(node);
return properties.some(function(property) {
var name = getPropertyName(property);
var isDisplayName = name === 'displayName';
var isPropTypes = name === 'propTypes' || name === 'props' && property.typeAnnotation;
var contextTypes = name === 'contextTypes';
var isUselessConstructor =
property.kind === 'constructor' &&
isRedundantSuperCall(property.value.body.body, property.value.params)
;
var isRender = name === 'render';
return !isDisplayName && !isPropTypes && !contextTypes && !isUselessConstructor && !isRender;
});
}
/**
* Mark component as pure as declared
* @param {ASTNode} node The AST node being checked.
*/
var markSCUAsDeclared = function (node) {
components.set(node, {
hasSCU: true
});
};
/**
* Mark childContextTypes as declared
* @param {ASTNode} node The AST node being checked.
*/
var markChildContextTypesAsDeclared = function (node) {
components.set(node, {
hasChildContextTypes: true
});
};
/**
* Mark a setState as used
* @param {ASTNode} node The AST node being checked.
*/
function markThisAsUsed(node) {
components.set(node, {
useThis: true
});
}
/**
* Mark a props or context as used
* @param {ASTNode} node The AST node being checked.
*/
function markPropsOrContextAsUsed(node) {
components.set(node, {
usePropsOrContext: true
});
}
/**
* Mark a ref as used
* @param {ASTNode} node The AST node being checked.
*/
function markRefAsUsed(node) {
components.set(node, {
useRef: true
});
}
/**
* Mark return as invalid
* @param {ASTNode} node The AST node being checked.
*/
function markReturnAsInvalid(node) {
components.set(node, {
invalidReturn: true
});
}
/**
* Mark a ClassDeclaration as having used decorators
* @param {ASTNode} node The AST node being checked.
*/
function markDecoratorsAsUsed(node) {
components.set(node, {
useDecorators: true
});
}
return {
ClassDeclaration: function (node) {
if (ignorePureComponents && utils.isPureComponent(node)) {
markSCUAsDeclared(node);
}
if (node.decorators && node.decorators.length) {
markDecoratorsAsUsed(node);
}
},
// Mark `this` destructuring as a usage of `this`
VariableDeclarator: function(node) {
// Ignore destructuring on other than `this`
if (!node.id || node.id.type !== 'ObjectPattern' || !node.init || node.init.type !== 'ThisExpression') {
return;
}
// Ignore `props` and `context`
var useThis = node.id.properties.some(function(property) {
var name = getPropertyName(property);
return name !== 'props' && name !== 'context';
});
if (!useThis) {
markPropsOrContextAsUsed(node);
return;
}
markThisAsUsed(node);
},
// Mark `this` usage
MemberExpression: function(node) {
if (node.object.type !== 'ThisExpression') {
if (node.property && node.property.name === 'childContextTypes') {
var component = utils.getRelatedComponent(node);
if (!component) {
return;
}
markChildContextTypesAsDeclared(component.node);
return;
}
return;
// Ignore calls to `this.props` and `this.context`
} else if (
(node.property.name || node.property.value) === 'props' ||
(node.property.name || node.property.value) === 'context'
) {
markPropsOrContextAsUsed(node);
return;
}
markThisAsUsed(node);
},
// Mark `ref` usage
JSXAttribute: function(node) {
var name = sourceCode.getText(node.name);
if (name !== 'ref') {
return;
}
markRefAsUsed(node);
},
// Mark `render` that do not return some JSX
ReturnStatement: function(node) {
var blockNode;
var scope = context.getScope();
while (scope) {
blockNode = scope.block && scope.block.parent;
if (blockNode && (blockNode.type === 'MethodDefinition' || blockNode.type === 'Property')) {
break;
}
scope = scope.upper;
}
var isRender = blockNode && blockNode.key && blockNode.key.name === 'render';
var allowNull = versionUtil.test(context, '15.0.0'); // Stateless components can return null since React 15
var isReturningJSX = utils.isReturningJSX(node, !allowNull);
var isReturningNull = node.argument && (node.argument.value === null || node.argument.value === false);
if (
!isRender ||
(allowNull && (isReturningJSX || isReturningNull)) ||
(!allowNull && isReturningJSX)
) {
return;
}
markReturnAsInvalid(node);
},
'Program:exit': function() {
var list = components.list();
for (var component in list) {
if (
!has(list, component) ||
hasOtherProperties(list[component].node) ||
list[component].useThis ||
list[component].useRef ||
list[component].invalidReturn ||
list[component].hasChildContextTypes ||
list[component].useDecorators ||
(!utils.isES5Component(list[component].node) && !utils.isES6Component(list[component].node))
) {
continue;
}
if (list[component].hasSCU && list[component].usePropsOrContext) {
continue;
}
context.report({
node: list[component].node,
message: 'Component should be written as a pure function'
});
}
}
};
})
};

943
web/node_modules/eslint-plugin-react/lib/rules/prop-types.js generated vendored Executable file
View File

@@ -0,0 +1,943 @@
/**
* @fileoverview Prevent missing props validation in a React component definition
* @author Yannick Croissant
*/
'use strict';
// As for exceptions for props.children or props.className (and alike) look at
// https://github.com/yannickcr/eslint-plugin-react/issues/7
var has = require('has');
var Components = require('../util/Components');
var variable = require('../util/variable');
var annotations = require('../util/annotations');
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
var PROPS_REGEX = /^(props|nextProps)$/;
var DIRECT_PROPS_REGEX = /^(props|nextProps)\s*(\.|\[)/;
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent missing props validation in a React component definition',
category: 'Best Practices',
recommended: true
},
schema: [{
type: 'object',
properties: {
ignore: {
type: 'array',
items: {
type: 'string'
}
},
customValidators: {
type: 'array',
items: {
type: 'string'
}
},
skipUndeclared: {
type: 'boolean'
}
},
additionalProperties: false
}]
},
create: Components.detect(function(context, components, utils) {
var sourceCode = context.getSourceCode();
var configuration = context.options[0] || {};
var ignored = configuration.ignore || [];
var customValidators = configuration.customValidators || [];
var skipUndeclared = configuration.skipUndeclared || false;
// Used to track the type annotations in scope.
// Necessary because babel's scopes do not track type annotations.
var stack = null;
var MISSING_MESSAGE = '\'{{name}}\' is missing in props validation';
/**
* Helper for accessing the current scope in the stack.
* @param {string} key The name of the identifier to access. If omitted, returns the full scope.
* @param {ASTNode} value If provided sets the new value for the identifier.
* @returns {Object|ASTNode} Either the whole scope or the ASTNode associated with the given identifier.
*/
function typeScope(key, value) {
if (arguments.length === 0) {
return stack[stack.length - 1];
} else if (arguments.length === 1) {
return stack[stack.length - 1][key];
}
stack[stack.length - 1][key] = value;
return value;
}
/**
* Check if we are in a class constructor
* @return {boolean} true if we are in a class constructor, false if not
*/
function inConstructor() {
var scope = context.getScope();
while (scope) {
if (scope.block && scope.block.parent && scope.block.parent.kind === 'constructor') {
return true;
}
scope = scope.upper;
}
return false;
}
/**
* Check if we are in a class constructor
* @return {boolean} true if we are in a class constructor, false if not
*/
function inComponentWillReceiveProps() {
var scope = context.getScope();
while (scope) {
if (
scope.block && scope.block.parent &&
scope.block.parent.key && scope.block.parent.key.name === 'componentWillReceiveProps'
) {
return true;
}
scope = scope.upper;
}
return false;
}
/**
* Checks if we are using a prop
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if we are using a prop, false if not.
*/
function isPropTypesUsage(node) {
var isClassUsage = (
(utils.getParentES6Component() || utils.getParentES5Component()) &&
node.object.type === 'ThisExpression' && node.property.name === 'props'
);
var isStatelessFunctionUsage = node.object.name === 'props';
var isNextPropsUsage = node.object.name === 'nextProps' && inComponentWillReceiveProps();
return isClassUsage || isStatelessFunctionUsage || isNextPropsUsage;
}
/**
* Checks if we are declaring a `props` class property with a flow type annotation.
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if the node is a type annotated props declaration, false if not.
*/
function isAnnotatedClassPropsDeclaration(node) {
if (node && node.type === 'ClassProperty') {
var tokens = context.getFirstTokens(node, 2);
if (
node.typeAnnotation && (
tokens[0].value === 'props' ||
(tokens[1] && tokens[1].value === 'props')
)
) {
return true;
}
}
return false;
}
/**
* Checks if we are declaring a prop
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if we are declaring a prop, false if not.
*/
function isPropTypesDeclaration(node) {
// Special case for class properties
// (babel-eslint does not expose property name so we have to rely on tokens)
if (node && node.type === 'ClassProperty') {
var tokens = context.getFirstTokens(node, 2);
if (
tokens[0].value === 'propTypes' ||
(tokens[1] && tokens[1].value === 'propTypes')
) {
return true;
}
return false;
}
return Boolean(
node &&
node.name === 'propTypes'
);
}
/**
* Checks if the prop is ignored
* @param {String} name Name of the prop to check.
* @returns {Boolean} True if the prop is ignored, false if not.
*/
function isIgnored(name) {
return ignored.indexOf(name) !== -1;
}
/**
* Checks if prop should be validated by plugin-react-proptypes
* @param {String} validator Name of validator to check.
* @returns {Boolean} True if validator should be checked by custom validator.
*/
function hasCustomValidator(validator) {
return customValidators.indexOf(validator) !== -1;
}
/**
* Checks if the component must be validated
* @param {Object} component The component to process
* @returns {Boolean} True if the component must be validated, false if not.
*/
function mustBeValidated(component) {
var isSkippedByConfig = skipUndeclared && typeof component.declaredPropTypes === 'undefined';
return Boolean(
component &&
component.usedPropTypes &&
!component.ignorePropsValidation &&
!isSkippedByConfig
);
}
/**
* Internal: Checks if the prop is declared
* @param {Object} declaredPropTypes Description of propTypes declared in the current component
* @param {String[]} keyList Dot separated name of the prop to check.
* @returns {Boolean} True if the prop is declared, false if not.
*/
function _isDeclaredInComponent(declaredPropTypes, keyList) {
for (var i = 0, j = keyList.length; i < j; i++) {
var key = keyList[i];
var propType = (
declaredPropTypes && (
// Check if this key is declared
(declaredPropTypes[key] || // If not, check if this type accepts any key
declaredPropTypes.__ANY_KEY__)
)
);
if (!propType) {
// If it's a computed property, we can't make any further analysis, but is valid
return key === '__COMPUTED_PROP__';
}
if (propType === true) {
return true;
}
// Consider every children as declared
if (propType.children === true) {
return true;
}
if (propType.acceptedProperties) {
return key in propType.acceptedProperties;
}
if (propType.type === 'union') {
// If we fall in this case, we know there is at least one complex type in the union
if (i + 1 >= j) {
// this is the last key, accept everything
return true;
}
// non trivial, check all of them
var unionTypes = propType.children;
var unionPropType = {};
for (var k = 0, z = unionTypes.length; k < z; k++) {
unionPropType[key] = unionTypes[k];
var isValid = _isDeclaredInComponent(
unionPropType,
keyList.slice(i)
);
if (isValid) {
return true;
}
}
// every possible union were invalid
return false;
}
declaredPropTypes = propType.children;
}
return true;
}
/**
* Checks if the prop is declared
* @param {ASTNode} node The AST node being checked.
* @param {String[]} names List of names of the prop to check.
* @returns {Boolean} True if the prop is declared, false if not.
*/
function isDeclaredInComponent(node, names) {
while (node) {
var component = components.get(node);
var isDeclared =
component && component.confidence === 2 &&
_isDeclaredInComponent(component.declaredPropTypes || {}, names)
;
if (isDeclared) {
return true;
}
node = node.parent;
}
return false;
}
/**
* Checks if the prop has spread operator.
* @param {ASTNode} node The AST node being marked.
* @returns {Boolean} True if the prop has spread operator, false if not.
*/
function hasSpreadOperator(node) {
var tokens = sourceCode.getTokens(node);
return tokens.length && tokens[0].value === '...';
}
/**
* Retrieve the name of a key node
* @param {ASTNode} node The AST node with the key.
* @return {string} the name of the key
*/
function getKeyValue(node) {
if (node.type === 'ObjectTypeProperty') {
var tokens = context.getFirstTokens(node, 2);
return (tokens[0].value === '+' || tokens[0].value === '-'
? tokens[1].value
: tokens[0].value
);
}
var key = node.key || node.argument;
return key.type === 'Identifier' ? key.name : key.value;
}
/**
* Iterates through a properties node, like a customized forEach.
* @param {Object[]} properties Array of properties to iterate.
* @param {Function} fn Function to call on each property, receives property key
and property value. (key, value) => void
*/
function iterateProperties(properties, fn) {
if (properties.length && typeof fn === 'function') {
for (var i = 0, j = properties.length; i < j; i++) {
var node = properties[i];
var key = getKeyValue(node);
var value = node.value;
fn(key, value);
}
}
}
/**
* Creates the representation of the React propTypes for the component.
* The representation is used to verify nested used properties.
* @param {ASTNode} value Node of the PropTypes for the desired property
* @return {Object|Boolean} The representation of the declaration, true means
* the property is declared without the need for further analysis.
*/
function buildReactDeclarationTypes(value) {
if (
value &&
value.callee &&
value.callee.object &&
hasCustomValidator(value.callee.object.name)
) {
return true;
}
if (
value &&
value.type === 'MemberExpression' &&
value.property &&
value.property.name &&
value.property.name === 'isRequired'
) {
value = value.object;
}
// Verify PropTypes that are functions
if (
value &&
value.type === 'CallExpression' &&
value.callee &&
value.callee.property &&
value.callee.property.name &&
value.arguments &&
value.arguments.length > 0
) {
var callName = value.callee.property.name;
var argument = value.arguments[0];
switch (callName) {
case 'shape':
if (argument.type !== 'ObjectExpression') {
// Invalid proptype or cannot analyse statically
return true;
}
var shapeTypeDefinition = {
type: 'shape',
children: {}
};
iterateProperties(argument.properties, function(childKey, childValue) {
shapeTypeDefinition.children[childKey] = buildReactDeclarationTypes(childValue);
});
return shapeTypeDefinition;
case 'arrayOf':
case 'objectOf':
return {
type: 'object',
children: {
__ANY_KEY__: buildReactDeclarationTypes(argument)
}
};
case 'oneOfType':
if (
!argument.elements ||
!argument.elements.length
) {
// Invalid proptype or cannot analyse statically
return true;
}
var unionTypeDefinition = {
type: 'union',
children: []
};
for (var i = 0, j = argument.elements.length; i < j; i++) {
var type = buildReactDeclarationTypes(argument.elements[i]);
// keep only complex type
if (type !== true) {
if (type.children === true) {
// every child is accepted for one type, abort type analysis
unionTypeDefinition.children = true;
return unionTypeDefinition;
}
}
unionTypeDefinition.children.push(type);
}
if (unionTypeDefinition.length === 0) {
// no complex type found, simply accept everything
return true;
}
return unionTypeDefinition;
case 'instanceOf':
return {
type: 'instance',
// Accept all children because we can't know what type they are
children: true
};
case 'oneOf':
default:
return true;
}
}
// Unknown property or accepts everything (any, object, ...)
return true;
}
/**
* Creates the representation of the React props type annotation for the component.
* The representation is used to verify nested used properties.
* @param {ASTNode} annotation Type annotation for the props class property.
* @return {Object|Boolean} The representation of the declaration, true means
* the property is declared without the need for further analysis.
*/
function buildTypeAnnotationDeclarationTypes(annotation) {
switch (annotation.type) {
case 'GenericTypeAnnotation':
if (typeScope(annotation.id.name)) {
return buildTypeAnnotationDeclarationTypes(typeScope(annotation.id.name));
}
return true;
case 'ObjectTypeAnnotation':
var shapeTypeDefinition = {
type: 'shape',
children: {}
};
iterateProperties(annotation.properties, function(childKey, childValue) {
shapeTypeDefinition.children[childKey] = buildTypeAnnotationDeclarationTypes(childValue);
});
return shapeTypeDefinition;
case 'UnionTypeAnnotation':
var unionTypeDefinition = {
type: 'union',
children: []
};
for (var i = 0, j = annotation.types.length; i < j; i++) {
var type = buildTypeAnnotationDeclarationTypes(annotation.types[i]);
// keep only complex type
if (type !== true) {
if (type.children === true) {
// every child is accepted for one type, abort type analysis
unionTypeDefinition.children = true;
return unionTypeDefinition;
}
}
unionTypeDefinition.children.push(type);
}
if (unionTypeDefinition.children.length === 0) {
// no complex type found, simply accept everything
return true;
}
return unionTypeDefinition;
case 'ArrayTypeAnnotation':
return {
type: 'object',
children: {
__ANY_KEY__: buildTypeAnnotationDeclarationTypes(annotation.elementType)
}
};
default:
// Unknown or accepts everything.
return true;
}
}
/**
* Retrieve the name of a property node
* @param {ASTNode} node The AST node with the property.
* @return {string} the name of the property or undefined if not found
*/
function getPropertyName(node) {
var isDirectProp = DIRECT_PROPS_REGEX.test(sourceCode.getText(node));
var isInClassComponent = utils.getParentES6Component() || utils.getParentES5Component();
var isNotInConstructor = !inConstructor();
var isNotInComponentWillReceiveProps = !inComponentWillReceiveProps();
if (isDirectProp && isInClassComponent && isNotInConstructor && isNotInComponentWillReceiveProps) {
return void 0;
}
if (!isDirectProp) {
node = node.parent;
}
var property = node.property;
if (property) {
switch (property.type) {
case 'Identifier':
if (node.computed) {
return '__COMPUTED_PROP__';
}
return property.name;
case 'MemberExpression':
return void 0;
case 'Literal':
// Accept computed properties that are literal strings
if (typeof property.value === 'string') {
return property.value;
}
// falls through
default:
if (node.computed) {
return '__COMPUTED_PROP__';
}
break;
}
}
return void 0;
}
/**
* Mark a prop type as used
* @param {ASTNode} node The AST node being marked.
*/
function markPropTypesAsUsed(node, parentNames) {
parentNames = parentNames || [];
var type;
var name;
var allNames;
var properties;
switch (node.type) {
case 'MemberExpression':
name = getPropertyName(node);
if (name) {
allNames = parentNames.concat(name);
if (node.parent.type === 'MemberExpression') {
markPropTypesAsUsed(node.parent, allNames);
}
// Do not mark computed props as used.
type = name !== '__COMPUTED_PROP__' ? 'direct' : null;
} else if (
node.parent.id &&
node.parent.id.properties &&
node.parent.id.properties.length &&
getKeyValue(node.parent.id.properties[0])
) {
type = 'destructuring';
properties = node.parent.id.properties;
}
break;
case 'ArrowFunctionExpression':
case 'FunctionDeclaration':
case 'FunctionExpression':
type = 'destructuring';
properties = node.params[0].properties;
break;
case 'VariableDeclarator':
for (var i = 0, j = node.id.properties.length; i < j; i++) {
// let {props: {firstname}} = this
var thisDestructuring = (
!hasSpreadOperator(node.id.properties[i]) &&
(PROPS_REGEX.test(node.id.properties[i].key.name) || PROPS_REGEX.test(node.id.properties[i].key.value)) &&
node.id.properties[i].value.type === 'ObjectPattern'
);
// let {firstname} = props
var directDestructuring =
PROPS_REGEX.test(node.init.name) &&
(utils.getParentStatelessComponent() || inConstructor() || inComponentWillReceiveProps())
;
if (thisDestructuring) {
properties = node.id.properties[i].value.properties;
} else if (directDestructuring) {
properties = node.id.properties;
} else {
continue;
}
type = 'destructuring';
break;
}
break;
default:
throw new Error(`${node.type} ASTNodes are not handled by markPropTypesAsUsed`);
}
var component = components.get(utils.getParentComponent());
var usedPropTypes = (component && component.usedPropTypes || []).slice();
switch (type) {
case 'direct':
// Ignore Object methods
if (Object.prototype[name]) {
break;
}
var isDirectProp = DIRECT_PROPS_REGEX.test(sourceCode.getText(node));
usedPropTypes.push({
name: name,
allNames: allNames,
node: (
!isDirectProp && !inConstructor() && !inComponentWillReceiveProps() ?
node.parent.property :
node.property
)
});
break;
case 'destructuring':
for (var k = 0, l = properties.length; k < l; k++) {
if (hasSpreadOperator(properties[k]) || properties[k].computed) {
continue;
}
var propName = getKeyValue(properties[k]);
var currentNode = node;
allNames = [];
while (currentNode.property && !PROPS_REGEX.test(currentNode.property.name)) {
allNames.unshift(currentNode.property.name);
currentNode = currentNode.object;
}
allNames.push(propName);
if (propName) {
usedPropTypes.push({
name: propName,
allNames: allNames,
node: properties[k]
});
}
}
break;
default:
break;
}
components.set(node, {
usedPropTypes: usedPropTypes
});
}
/**
* Mark a prop type as declared
* @param {ASTNode} node The AST node being checked.
* @param {propTypes} node The AST node containing the proptypes
*/
function markPropTypesAsDeclared(node, propTypes) {
var componentNode = node;
while (componentNode && !components.get(componentNode)) {
componentNode = componentNode.parent;
}
var component = components.get(componentNode);
var declaredPropTypes = component && component.declaredPropTypes || {};
var ignorePropsValidation = false;
switch (propTypes && propTypes.type) {
case 'ObjectTypeAnnotation':
iterateProperties(propTypes.properties, function(key, value) {
if (!value) {
ignorePropsValidation = true;
return;
}
declaredPropTypes[key] = buildTypeAnnotationDeclarationTypes(value);
});
break;
case 'ObjectExpression':
iterateProperties(propTypes.properties, function(key, value) {
if (!value) {
ignorePropsValidation = true;
return;
}
declaredPropTypes[key] = buildReactDeclarationTypes(value);
});
break;
case 'MemberExpression':
var curDeclaredPropTypes = declaredPropTypes;
// Walk the list of properties, until we reach the assignment
// ie: ClassX.propTypes.a.b.c = ...
while (
propTypes &&
propTypes.parent &&
propTypes.parent.type !== 'AssignmentExpression' &&
propTypes.property &&
curDeclaredPropTypes
) {
var propName = propTypes.property.name;
if (propName in curDeclaredPropTypes) {
curDeclaredPropTypes = curDeclaredPropTypes[propName].children;
propTypes = propTypes.parent;
} else {
// This will crash at runtime because we haven't seen this key before
// stop this and do not declare it
propTypes = null;
}
}
if (propTypes && propTypes.parent && propTypes.property) {
curDeclaredPropTypes[propTypes.property.name] =
buildReactDeclarationTypes(propTypes.parent.right);
} else {
ignorePropsValidation = true;
}
break;
case 'Identifier':
var variablesInScope = variable.variablesInScope(context);
for (var i = 0, j = variablesInScope.length; i < j; i++) {
if (variablesInScope[i].name !== propTypes.name) {
continue;
}
var defInScope = variablesInScope[i].defs[variablesInScope[i].defs.length - 1];
markPropTypesAsDeclared(node, defInScope.node && defInScope.node.init);
return;
}
ignorePropsValidation = true;
break;
case null:
break;
default:
ignorePropsValidation = true;
break;
}
components.set(node, {
declaredPropTypes: declaredPropTypes,
ignorePropsValidation: ignorePropsValidation
});
}
/**
* Reports undeclared proptypes for a given component
* @param {Object} component The component to process
*/
function reportUndeclaredPropTypes(component) {
var allNames;
for (var i = 0, j = component.usedPropTypes.length; i < j; i++) {
allNames = component.usedPropTypes[i].allNames;
if (
isIgnored(allNames[0]) ||
isDeclaredInComponent(component.node, allNames)
) {
continue;
}
context.report(
component.usedPropTypes[i].node,
MISSING_MESSAGE, {
name: allNames.join('.').replace(/\.__COMPUTED_PROP__/g, '[]')
}
);
}
}
/**
* Resolve the type annotation for a given node.
* Flow annotations are sometimes wrapped in outer `TypeAnnotation`
* and `NullableTypeAnnotation` nodes which obscure the annotation we're
* interested in.
* This method also resolves type aliases where possible.
*
* @param {ASTNode} node The annotation or a node containing the type annotation.
* @returns {ASTNode} The resolved type annotation for the node.
*/
function resolveTypeAnnotation(node) {
var annotation = node.typeAnnotation || node;
while (annotation && (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation')) {
annotation = annotation.typeAnnotation;
}
if (annotation.type === 'GenericTypeAnnotation' && typeScope(annotation.id.name)) {
return typeScope(annotation.id.name);
}
return annotation;
}
/**
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
* FunctionDeclaration, or FunctionExpression
*/
function markDestructuredFunctionArgumentsAsUsed(node) {
var destructuring = node.params && node.params[0] && node.params[0].type === 'ObjectPattern';
if (destructuring && components.get(node)) {
markPropTypesAsUsed(node);
}
}
/**
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
* FunctionDeclaration, or FunctionExpression
*/
function markAnnotatedFunctionArgumentsAsDeclared(node) {
if (!node.params || !node.params.length || !annotations.isAnnotatedFunctionPropsDeclaration(node, context)) {
return;
}
markPropTypesAsDeclared(node, resolveTypeAnnotation(node.params[0]));
}
/**
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
* FunctionDeclaration, or FunctionExpression
*/
function handleStatelessComponent(node) {
markDestructuredFunctionArgumentsAsUsed(node);
markAnnotatedFunctionArgumentsAsDeclared(node);
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
ClassProperty: function(node) {
if (isAnnotatedClassPropsDeclaration(node)) {
markPropTypesAsDeclared(node, resolveTypeAnnotation(node));
} else if (isPropTypesDeclaration(node)) {
markPropTypesAsDeclared(node, node.value);
}
},
VariableDeclarator: function(node) {
var destructuring = node.init && node.id && node.id.type === 'ObjectPattern';
// let {props: {firstname}} = this
var thisDestructuring = destructuring && node.init.type === 'ThisExpression';
// let {firstname} = props
var directDestructuring =
destructuring &&
PROPS_REGEX.test(node.init.name) &&
(utils.getParentStatelessComponent() || inConstructor() || inComponentWillReceiveProps())
;
if (!thisDestructuring && !directDestructuring) {
return;
}
markPropTypesAsUsed(node);
},
FunctionDeclaration: handleStatelessComponent,
ArrowFunctionExpression: handleStatelessComponent,
FunctionExpression: handleStatelessComponent,
MemberExpression: function(node) {
var type;
if (isPropTypesUsage(node)) {
type = 'usage';
} else if (isPropTypesDeclaration(node.property)) {
type = 'declaration';
}
switch (type) {
case 'usage':
markPropTypesAsUsed(node);
break;
case 'declaration':
var component = utils.getRelatedComponent(node);
if (!component) {
return;
}
markPropTypesAsDeclared(component.node, node.parent.right || node.parent);
break;
default:
break;
}
},
MethodDefinition: function(node) {
if (!node.static || node.kind !== 'get' || !isPropTypesDeclaration(node.key)) {
return;
}
var i = node.value.body.body.length - 1;
for (; i >= 0; i--) {
if (node.value.body.body[i].type === 'ReturnStatement') {
break;
}
}
if (i >= 0) {
markPropTypesAsDeclared(node, node.value.body.body[i].argument);
}
},
ObjectExpression: function(node) {
// Search for the proptypes declaration
node.properties.forEach(function(property) {
if (!isPropTypesDeclaration(property.key)) {
return;
}
markPropTypesAsDeclared(node, property.value);
});
},
TypeAlias: function(node) {
typeScope(node.id.name, node.right);
},
Program: function() {
stack = [{}];
},
BlockStatement: function () {
stack.push(Object.create(typeScope()));
},
'BlockStatement:exit': function () {
stack.pop();
},
'Program:exit': function() {
stack = null;
var list = components.list();
// Report undeclared proptypes for all classes
for (var component in list) {
if (!has(list, component) || !mustBeValidated(list[component])) {
continue;
}
reportUndeclaredPropTypes(list[component]);
}
}
};
})
};

View File

@@ -0,0 +1,48 @@
/**
* @fileoverview Prevent missing React when using JSX
* @author Glen Mailer
*/
'use strict';
var variableUtil = require('../util/variable');
var pragmaUtil = require('../util/pragma');
// -----------------------------------------------------------------------------
// Rule Definition
// -----------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent missing React when using JSX',
category: 'Possible Errors',
recommended: true
},
schema: []
},
create: function(context) {
var pragma = pragmaUtil.getFromContext(context);
var NOT_DEFINED_MESSAGE = '\'{{name}}\' must be in scope when using JSX';
return {
JSXOpeningElement: function(node) {
var variables = variableUtil.variablesInScope(context);
if (variableUtil.findVariable(variables, pragma)) {
return;
}
context.report({
node: node,
message: NOT_DEFINED_MESSAGE,
data: {
name: pragma
}
});
}
};
}
};

View File

@@ -0,0 +1,595 @@
/**
* @fileOverview Enforce a defaultProps definition for every prop that is not a required prop.
* @author Vitor Balocco
*/
'use strict';
var has = require('has');
var Components = require('../util/Components');
var variableUtil = require('../util/variable');
var annotations = require('../util/annotations');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Enforce a defaultProps definition for every prop that is not a required prop.',
category: 'Best Practices'
},
schema: []
},
create: Components.detect(function(context, components, utils) {
/**
* Get properties name
* @param {Object} node - Property.
* @returns {String} Property name.
*/
function getPropertyName(node) {
if (node.key || ['MethodDefinition', 'Property'].indexOf(node.type) !== -1) {
return node.key.name;
} else if (node.type === 'MemberExpression') {
return node.property.name;
// Special case for class properties
// (babel-eslint@5 does not expose property name so we have to rely on tokens)
} else if (node.type === 'ClassProperty') {
var tokens = context.getFirstTokens(node, 2);
return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value;
}
return '';
}
/**
* Checks if the Identifier node passed in looks like a propTypes declaration.
* @param {ASTNode} node The node to check. Must be an Identifier node.
* @returns {Boolean} `true` if the node is a propTypes declaration, `false` if not
*/
function isPropTypesDeclaration(node) {
return getPropertyName(node) === 'propTypes';
}
/**
* Checks if the Identifier node passed in looks like a defaultProps declaration.
* @param {ASTNode} node The node to check. Must be an Identifier node.
* @returns {Boolean} `true` if the node is a defaultProps declaration, `false` if not
*/
function isDefaultPropsDeclaration(node) {
return (getPropertyName(node) === 'defaultProps' || getPropertyName(node) === 'getDefaultProps');
}
/**
* Checks if the PropTypes MemberExpression node passed in declares a required propType.
* @param {ASTNode} propTypeExpression node to check. Must be a `PropTypes` MemberExpression.
* @returns {Boolean} `true` if this PropType is required, `false` if not.
*/
function isRequiredPropType(propTypeExpression) {
return propTypeExpression.type === 'MemberExpression' && propTypeExpression.property.name === 'isRequired';
}
/**
* Find a variable by name in the current scope.
* @param {string} name Name of the variable to look for.
* @returns {ASTNode|null} Return null if the variable could not be found, ASTNode otherwise.
*/
function findVariableByName(name) {
var variable = variableUtil.variablesInScope(context).find(function(item) {
return item.name === name;
});
if (!variable || !variable.defs[0] || !variable.defs[0].node) {
return null;
}
if (variable.defs[0].node.type === 'TypeAlias') {
return variable.defs[0].node.right;
}
return variable.defs[0].node.init;
}
/**
* Try to resolve the node passed in to a variable in the current scope. If the node passed in is not
* an Identifier, then the node is simply returned.
* @param {ASTNode} node The node to resolve.
* @returns {ASTNode|null} Return null if the value could not be resolved, ASTNode otherwise.
*/
function resolveNodeValue(node) {
if (node.type === 'Identifier') {
return findVariableByName(node.name);
}
return node;
}
/**
* Tries to find the definition of a GenericTypeAnnotation in the current scope.
* @param {ASTNode} node The node GenericTypeAnnotation node to resolve.
* @return {ASTNode|null} Return null if definition cannot be found, ASTNode otherwise.
*/
function resolveGenericTypeAnnotation(node) {
if (node.type !== 'GenericTypeAnnotation' || node.id.type !== 'Identifier') {
return null;
}
return findVariableByName(node.id.name);
}
function resolveUnionTypeAnnotation(node) {
// Go through all the union and resolve any generic types.
return node.types.map(function(annotation) {
if (annotation.type === 'GenericTypeAnnotation') {
return resolveGenericTypeAnnotation(annotation);
}
return annotation;
});
}
/**
* Extracts a PropType from an ObjectExpression node.
* @param {ASTNode} objectExpression ObjectExpression node.
* @returns {Object[]} Array of PropType object representations, to be consumed by `addPropTypesToComponent`.
*/
function getPropTypesFromObjectExpression(objectExpression) {
var props = objectExpression.properties.filter(function(property) {
return property.type !== 'ExperimentalSpreadProperty';
});
return props.map(function(property) {
return {
name: property.key.name || property.key.value,
isRequired: isRequiredPropType(property.value),
node: property
};
});
}
/**
* Extracts a PropType from a TypeAnnotation node.
* @param {ASTNode} node TypeAnnotation node.
* @returns {Object[]} Array of PropType object representations, to be consumed by `addPropTypesToComponent`.
*/
function getPropTypesFromTypeAnnotation(node) {
var properties;
switch (node.typeAnnotation.type) {
case 'GenericTypeAnnotation':
var annotation = resolveGenericTypeAnnotation(node.typeAnnotation);
if (annotation && annotation.id) {
annotation = findVariableByName(annotation.id.name);
}
properties = annotation ? (annotation.properties || []) : [];
break;
case 'UnionTypeAnnotation':
var union = resolveUnionTypeAnnotation(node.typeAnnotation);
properties = union.reduce(function(acc, curr) {
if (!curr) {
return acc;
}
return acc.concat(curr.properties);
}, []);
break;
case 'ObjectTypeAnnotation':
properties = node.typeAnnotation.properties;
break;
default:
properties = [];
break;
}
var props = properties.filter(function(property) {
return property.type === 'ObjectTypeProperty';
});
return props.map(function(property) {
// the `key` property is not present in ObjectTypeProperty nodes, so we need to get the key name manually.
var tokens = context.getFirstTokens(property, 1);
var name = tokens[0].value;
return {
name: name,
isRequired: !property.optional,
node: property
};
});
}
/**
* Extracts a DefaultProp from an ObjectExpression node.
* @param {ASTNode} objectExpression ObjectExpression node.
* @returns {Object|string} Object representation of a defaultProp, to be consumed by
* `addDefaultPropsToComponent`, or string "unresolved", if the defaultProps
* from this ObjectExpression can't be resolved.
*/
function getDefaultPropsFromObjectExpression(objectExpression) {
var hasSpread = objectExpression.properties.find(function(property) {
return property.type === 'ExperimentalSpreadProperty';
});
if (hasSpread) {
return 'unresolved';
}
return objectExpression.properties.map(function(property) {
return property.key.name;
});
}
/**
* Marks a component's DefaultProps declaration as "unresolved". A component's DefaultProps is
* marked as "unresolved" if we cannot safely infer the values of its defaultProps declarations
* without risking false negatives.
* @param {Object} component The component to mark.
* @returns {void}
*/
function markDefaultPropsAsUnresolved(component) {
components.set(component.node, {
defaultProps: 'unresolved'
});
}
/**
* Adds propTypes to the component passed in.
* @param {ASTNode} component The component to add the propTypes to.
* @param {Object[]} propTypes propTypes to add to the component.
* @returns {void}
*/
function addPropTypesToComponent(component, propTypes) {
var props = component.propTypes || [];
components.set(component.node, {
propTypes: props.concat(propTypes)
});
}
/**
* Adds defaultProps to the component passed in.
* @param {ASTNode} component The component to add the defaultProps to.
* @param {String[]|String} defaultProps defaultProps to add to the component or the string "unresolved"
* if this component has defaultProps that can't be resolved.
* @returns {void}
*/
function addDefaultPropsToComponent(component, defaultProps) {
// Early return if this component's defaultProps is already marked as "unresolved".
if (component.defaultProps === 'unresolved') {
return;
}
if (defaultProps === 'unresolved') {
markDefaultPropsAsUnresolved(component);
return;
}
var defaults = component.defaultProps || {};
defaultProps.forEach(function(defaultProp) {
defaults[defaultProp] = true;
});
components.set(component.node, {
defaultProps: defaults
});
}
/**
* Tries to find a props type annotation in a stateless component.
* @param {ASTNode} node The AST node to look for a props type annotation.
* @return {void}
*/
function handleStatelessComponent(node) {
if (!node.params || !node.params.length || !annotations.isAnnotatedFunctionPropsDeclaration(node, context)) {
return;
}
// find component this props annotation belongs to
var component = components.get(utils.getParentStatelessComponent());
if (!component) {
return;
}
addPropTypesToComponent(component, getPropTypesFromTypeAnnotation(node.params[0].typeAnnotation, context));
}
function handlePropTypeAnnotationClassProperty(node) {
// find component this props annotation belongs to
var component = components.get(utils.getParentES6Component());
if (!component) {
return;
}
addPropTypesToComponent(component, getPropTypesFromTypeAnnotation(node.typeAnnotation, context));
}
function isPropTypeAnnotation(node) {
return (getPropertyName(node) === 'props' && !!node.typeAnnotation);
}
/**
* Reports all propTypes passed in that don't have a defaultProp counterpart.
* @param {Object[]} propTypes List of propTypes to check.
* @param {Object} defaultProps Object of defaultProps to check. Keys are the props names.
* @return {void}
*/
function reportPropTypesWithoutDefault(propTypes, defaultProps) {
// If this defaultProps is "unresolved", then we should ignore this component and not report
// any errors for it, to avoid false-positives with e.g. external defaultProps declarations or spread operators.
if (defaultProps === 'unresolved') {
return;
}
propTypes.forEach(function(prop) {
if (prop.isRequired) {
return;
}
if (defaultProps[prop.name]) {
return;
}
context.report(
prop.node,
'propType "{{name}}" is not required, but has no corresponding defaultProp declaration.',
{name: prop.name}
);
});
}
// --------------------------------------------------------------------------
// Public API
// --------------------------------------------------------------------------
return {
MemberExpression: function(node) {
var isPropType = isPropTypesDeclaration(node);
var isDefaultProp = isDefaultPropsDeclaration(node);
if (!isPropType && !isDefaultProp) {
return;
}
// find component this propTypes/defaultProps belongs to
var component = utils.getRelatedComponent(node);
if (!component) {
return;
}
// e.g.:
// MyComponent.propTypes = {
// foo: PropTypes.string.isRequired,
// bar: PropTypes.string
// };
//
// or:
//
// MyComponent.propTypes = myPropTypes;
if (node.parent.type === 'AssignmentExpression') {
var expression = resolveNodeValue(node.parent.right);
if (!expression || expression.type !== 'ObjectExpression') {
// If a value can't be found, we mark the defaultProps declaration as "unresolved", because
// we should ignore this component and not report any errors for it, to avoid false-positives
// with e.g. external defaultProps declarations.
if (isDefaultProp) {
markDefaultPropsAsUnresolved(component);
}
return;
}
if (isPropType) {
addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression));
} else {
addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression));
}
return;
}
// e.g.:
// MyComponent.propTypes.baz = PropTypes.string;
if (node.parent.type === 'MemberExpression' && node.parent.parent.type === 'AssignmentExpression') {
if (isPropType) {
addPropTypesToComponent(component, [{
name: node.parent.property.name,
isRequired: isRequiredPropType(node.parent.parent.right),
node: node.parent.parent
}]);
} else {
addDefaultPropsToComponent(component, [node.parent.property.name]);
}
return;
}
},
// e.g.:
// class Hello extends React.Component {
// static get propTypes() {
// return {
// name: PropTypes.string
// };
// }
// static get defaultProps() {
// return {
// name: 'Dean'
// };
// }
// render() {
// return <div>Hello {this.props.name}</div>;
// }
// }
MethodDefinition: function(node) {
if (!node.static || node.kind !== 'get') {
return;
}
var isPropType = isPropTypesDeclaration(node);
var isDefaultProp = isDefaultPropsDeclaration(node);
if (!isPropType && !isDefaultProp) {
return;
}
// find component this propTypes/defaultProps belongs to
var component = components.get(utils.getParentES6Component());
if (!component) {
return;
}
var returnStatement = utils.findReturnStatement(node);
if (!returnStatement) {
return;
}
var expression = resolveNodeValue(returnStatement.argument);
if (!expression || expression.type !== 'ObjectExpression') {
return;
}
if (isPropType) {
addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression));
} else {
addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression));
}
},
// e.g.:
// class Greeting extends React.Component {
// render() {
// return (
// <h1>Hello, {this.props.foo} {this.props.bar}</h1>
// );
// }
// static propTypes = {
// foo: PropTypes.string,
// bar: PropTypes.string.isRequired
// };
// }
ClassProperty: function(node) {
if (isPropTypeAnnotation(node)) {
handlePropTypeAnnotationClassProperty(node);
return;
}
if (!node.static) {
return;
}
if (!node.value) {
return;
}
var isPropType = getPropertyName(node) === 'propTypes';
var isDefaultProp = getPropertyName(node) === 'defaultProps' || getPropertyName(node) === 'getDefaultProps';
if (!isPropType && !isDefaultProp) {
return;
}
// find component this propTypes/defaultProps belongs to
var component = components.get(utils.getParentES6Component());
if (!component) {
return;
}
var expression = resolveNodeValue(node.value);
if (!expression || expression.type !== 'ObjectExpression') {
return;
}
if (isPropType) {
addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression));
} else {
addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression));
}
},
// e.g.:
// createReactClass({
// render: function() {
// return <div>{this.props.foo}</div>;
// },
// propTypes: {
// foo: PropTypes.string.isRequired,
// },
// getDefaultProps: function() {
// return {
// foo: 'default'
// };
// }
// });
ObjectExpression: function(node) {
// find component this propTypes/defaultProps belongs to
var component = utils.isES5Component(node) && components.get(node);
if (!component) {
return;
}
// Search for the proptypes declaration
node.properties.forEach(function(property) {
if (property.type === 'ExperimentalSpreadProperty') {
return;
}
var isPropType = isPropTypesDeclaration(property);
var isDefaultProp = isDefaultPropsDeclaration(property);
if (!isPropType && !isDefaultProp) {
return;
}
if (isPropType && property.value.type === 'ObjectExpression') {
addPropTypesToComponent(component, getPropTypesFromObjectExpression(property.value));
return;
}
if (isDefaultProp && property.value.type === 'FunctionExpression') {
var returnStatement = utils.findReturnStatement(property);
if (!returnStatement || returnStatement.argument.type !== 'ObjectExpression') {
return;
}
addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(returnStatement.argument));
}
});
},
// Check for type annotations in stateless components
FunctionDeclaration: handleStatelessComponent,
ArrowFunctionExpression: handleStatelessComponent,
FunctionExpression: handleStatelessComponent,
'Program:exit': function() {
var list = components.list();
for (var component in list) {
if (!has(list, component)) {
continue;
}
// If no propTypes could be found, we don't report anything.
if (!list[component].propTypes) {
return;
}
reportPropTypesWithoutDefault(
list[component].propTypes,
list[component].defaultProps || {}
);
}
}
};
})
};

View File

@@ -0,0 +1,231 @@
/**
* @fileoverview Enforce React components to have a shouldComponentUpdate method
* @author Evgueni Naverniouk
*/
'use strict';
var has = require('has');
var Components = require('../util/Components');
module.exports = {
meta: {
docs: {
description: 'Enforce React components to have a shouldComponentUpdate method',
category: 'Best Practices',
recommended: false
},
schema: [{
type: 'object',
properties: {
allowDecorators: {
type: 'array',
items: {
type: 'string'
}
}
},
additionalProperties: false
}]
},
create: Components.detect(function (context, components, utils) {
var MISSING_MESSAGE = 'Component is not optimized. Please add a shouldComponentUpdate method.';
var configuration = context.options[0] || {};
var allowDecorators = configuration.allowDecorators || [];
/**
* Checks to see if our component is decorated by PureRenderMixin via reactMixin
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if node is decorated with a PureRenderMixin, false if not.
*/
var hasPureRenderDecorator = function (node) {
if (node.decorators && node.decorators.length) {
for (var i = 0, l = node.decorators.length; i < l; i++) {
if (
node.decorators[i].expression &&
node.decorators[i].expression.callee &&
node.decorators[i].expression.callee.object &&
node.decorators[i].expression.callee.object.name === 'reactMixin' &&
node.decorators[i].expression.callee.property &&
node.decorators[i].expression.callee.property.name === 'decorate' &&
node.decorators[i].expression.arguments &&
node.decorators[i].expression.arguments.length &&
node.decorators[i].expression.arguments[0].name === 'PureRenderMixin'
) {
return true;
}
}
}
return false;
};
/**
* Checks to see if our component is custom decorated
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if node is decorated name with a custom decorated, false if not.
*/
var hasCustomDecorator = function (node) {
var allowLength = allowDecorators.length;
if (allowLength && node.decorators && node.decorators.length) {
for (var i = 0; i < allowLength; i++) {
for (var j = 0, l = node.decorators.length; j < l; j++) {
if (
node.decorators[j].expression &&
node.decorators[j].expression.name === allowDecorators[i]
) {
return true;
}
}
}
}
return false;
};
/**
* Checks if we are declaring a shouldComponentUpdate method
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if we are declaring a shouldComponentUpdate method, false if not.
*/
var isSCUDeclarеd = function (node) {
return Boolean(
node &&
node.name === 'shouldComponentUpdate'
);
};
/**
* Checks if we are declaring a PureRenderMixin mixin
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if we are declaring a PureRenderMixin method, false if not.
*/
var isPureRenderDeclared = function (node) {
var hasPR = false;
if (node.value && node.value.elements) {
for (var i = 0, l = node.value.elements.length; i < l; i++) {
if (node.value.elements[i].name === 'PureRenderMixin') {
hasPR = true;
break;
}
}
}
return Boolean(
node &&
node.key.name === 'mixins' &&
hasPR
);
};
/**
* Mark shouldComponentUpdate as declared
* @param {ASTNode} node The AST node being checked.
*/
var markSCUAsDeclared = function (node) {
components.set(node, {
hasSCU: true
});
};
/**
* Reports missing optimization for a given component
* @param {Object} component The component to process
*/
var reportMissingOptimization = function (component) {
context.report({
node: component.node,
message: MISSING_MESSAGE,
data: {
component: component.name
}
});
};
/**
* Checks if we are declaring function in class
* @returns {Boolean} True if we are declaring function in class, false if not.
*/
var isFunctionInClass = function () {
var blockNode;
var scope = context.getScope();
while (scope) {
blockNode = scope.block;
if (blockNode && blockNode.type === 'ClassDeclaration') {
return true;
}
scope = scope.upper;
}
return false;
};
return {
ArrowFunctionExpression: function (node) {
// Stateless Functional Components cannot be optimized (yet)
markSCUAsDeclared(node);
},
ClassDeclaration: function (node) {
if (!(hasPureRenderDecorator(node) || hasCustomDecorator(node) || utils.isPureComponent(node))) {
return;
}
markSCUAsDeclared(node);
},
FunctionDeclaration: function (node) {
// Skip if the function is declared in the class
if (isFunctionInClass()) {
return;
}
// Stateless Functional Components cannot be optimized (yet)
markSCUAsDeclared(node);
},
FunctionExpression: function (node) {
// Skip if the function is declared in the class
if (isFunctionInClass()) {
return;
}
// Stateless Functional Components cannot be optimized (yet)
markSCUAsDeclared(node);
},
MethodDefinition: function (node) {
if (!isSCUDeclarеd(node.key)) {
return;
}
markSCUAsDeclared(node);
},
ObjectExpression: function (node) {
// Search for the shouldComponentUpdate declaration
for (var i = 0, l = node.properties.length; i < l; i++) {
if (
!node.properties[i].key || (
!isSCUDeclarеd(node.properties[i].key) &&
!isPureRenderDeclared(node.properties[i])
)
) {
continue;
}
markSCUAsDeclared(node);
}
},
'Program:exit': function () {
var list = components.list();
// Report missing shouldComponentUpdate for all components
for (var component in list) {
if (!has(list, component) || list[component].hasSCU) {
continue;
}
reportMissingOptimization(list[component]);
}
}
};
})
};

View File

@@ -0,0 +1,130 @@
/**
* @fileoverview Enforce ES5 or ES6 class for returning value in render function.
* @author Mark Orel
*/
'use strict';
var has = require('has');
var Components = require('../util/Components');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Enforce ES5 or ES6 class for returning value in render function',
category: 'Possible Errors',
recommended: true
},
schema: [{}]
},
create: Components.detect(function(context, components, utils) {
/**
* Mark a return statement as present
* @param {ASTNode} node The AST node being checked.
*/
function markReturnStatementPresent(node) {
components.set(node, {
hasReturnStatement: true
});
}
/**
* Get properties for a given AST node
* @param {ASTNode} node The AST node being checked.
* @returns {Array} Properties array.
*/
function getComponentProperties(node) {
switch (node.type) {
case 'ClassDeclaration':
return node.body.body;
case 'ObjectExpression':
return node.properties;
default:
return [];
}
}
/**
* Get properties name
* @param {Object} node - Property.
* @returns {String} Property name.
*/
function getPropertyName(node) {
// Special case for class properties
// (babel-eslint does not expose property name so we have to rely on tokens)
if (node.type === 'ClassProperty') {
var tokens = context.getFirstTokens(node, 2);
return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value;
} else if (['MethodDefinition', 'Property'].indexOf(node.type) !== -1) {
return node.key.name;
}
return '';
}
/**
* Check if a given AST node has a render method
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if there is a render method, false if not
*/
function hasRenderMethod(node) {
var properties = getComponentProperties(node);
for (var i = 0, j = properties.length; i < j; i++) {
if (getPropertyName(properties[i]) !== 'render' || !properties[i].value) {
continue;
}
return /FunctionExpression$/.test(properties[i].value.type);
}
return false;
}
return {
ReturnStatement: function(node) {
var ancestors = context.getAncestors(node).reverse();
var depth = 0;
for (var i = 0, j = ancestors.length; i < j; i++) {
if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) {
depth++;
}
if (
!/(MethodDefinition|(Class)?Property)$/.test(ancestors[i].type) ||
getPropertyName(ancestors[i]) !== 'render' ||
depth > 1
) {
continue;
}
markReturnStatementPresent(node);
}
},
ArrowFunctionExpression: function(node) {
if (node.expression === false || getPropertyName(node.parent) !== 'render') {
return;
}
markReturnStatementPresent(node);
},
'Program:exit': function() {
var list = components.list();
for (var component in list) {
if (
!has(list, component) ||
!hasRenderMethod(list[component].node) ||
list[component].hasReturnStatement ||
(!utils.isES5Component(list[component].node) && !utils.isES6Component(list[component].node))
) {
continue;
}
context.report({
node: list[component].node,
message: 'Your render method should have return statement'
});
}
}
};
})
};

View File

@@ -0,0 +1,95 @@
/**
* @fileoverview Prevent extra closing tags for components without children
* @author Yannick Croissant
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent extra closing tags for components without children',
category: 'Stylistic Issues',
recommended: false
},
fixable: 'code',
schema: [{
type: 'object',
properties: {
component: {
default: true,
type: 'boolean'
},
html: {
default: true,
type: 'boolean'
}
},
additionalProperties: false
}]
},
create: function(context) {
var tagConvention = /^[a-z]|\-/;
function isTagName(name) {
return tagConvention.test(name);
}
function isComponent(node) {
return node.name && node.name.type === 'JSXIdentifier' && !isTagName(node.name.name);
}
function hasChildren(node) {
var childrens = node.parent.children;
if (
!childrens.length ||
(childrens.length === 1 && childrens[0].type === 'Literal' && !childrens[0].value.replace(/(?!\xA0)\s/g, ''))
) {
return false;
}
return true;
}
function isShouldBeSelfClosed(node) {
var configuration = context.options[0] || {component: true, html: true};
return (
configuration.component && isComponent(node) ||
configuration.html && isTagName(node.name.name)
) && !node.selfClosing && !hasChildren(node);
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
JSXOpeningElement: function(node) {
if (!isShouldBeSelfClosed(node)) {
return;
}
context.report({
node: node,
message: 'Empty components are self-closing',
fix: function(fixer) {
// Represents the last character of the JSXOpeningElement, the '>' character
var openingElementEnding = node.end - 1;
// Represents the last character of the JSXClosingElement, the '>' character
var closingElementEnding = node.parent.closingElement.end;
// Replace />.*<\/.*>/ with '/>'
var range = [openingElementEnding, closingElementEnding];
return fixer.replaceTextRange(range, ' />');
}
});
}
};
}
};

428
web/node_modules/eslint-plugin-react/lib/rules/sort-comp.js generated vendored Executable file
View File

@@ -0,0 +1,428 @@
/**
* @fileoverview Enforce component methods order
* @author Yannick Croissant
*/
'use strict';
var has = require('has');
var util = require('util');
var Components = require('../util/Components');
/**
* Get the methods order from the default config and the user config
* @param {Object} defaultConfig The default configuration.
* @param {Object} userConfig The user configuration.
* @returns {Array} Methods order
*/
function getMethodsOrder(defaultConfig, userConfig) {
userConfig = userConfig || {};
var groups = util._extend(defaultConfig.groups, userConfig.groups);
var order = userConfig.order || defaultConfig.order;
var config = [];
var entry;
for (var i = 0, j = order.length; i < j; i++) {
entry = order[i];
if (has(groups, entry)) {
config = config.concat(groups[entry]);
} else {
config.push(entry);
}
}
return config;
}
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Enforce component methods order',
category: 'Stylistic Issues',
recommended: false
},
schema: [{
type: 'object',
properties: {
order: {
type: 'array',
items: {
type: 'string'
}
},
groups: {
type: 'object',
patternProperties: {
'^.*$': {
type: 'array',
items: {
type: 'string'
}
}
}
}
},
additionalProperties: false
}]
},
create: Components.detect(function(context, components) {
var errors = {};
var MISPOSITION_MESSAGE = '{{propA}} should be placed {{position}} {{propB}}';
var methodsOrder = getMethodsOrder({
order: [
'static-methods',
'lifecycle',
'everything-else',
'render'
],
groups: {
lifecycle: [
'displayName',
'propTypes',
'contextTypes',
'childContextTypes',
'mixins',
'statics',
'defaultProps',
'constructor',
'getDefaultProps',
'state',
'getInitialState',
'getChildContext',
'componentWillMount',
'componentDidMount',
'componentWillReceiveProps',
'shouldComponentUpdate',
'componentWillUpdate',
'componentDidUpdate',
'componentWillUnmount'
]
}
}, context.options[0]);
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
var regExpRegExp = /\/(.*)\/([g|y|i|m]*)/;
/**
* Get indexes of the matching patterns in methods order configuration
* @param {Object} method - Method metadata.
* @returns {Array} The matching patterns indexes. Return [Infinity] if there is no match.
*/
function getRefPropIndexes(method) {
var isRegExp;
var matching;
var i;
var j;
var indexes = [];
if (method.static) {
for (i = 0, j = methodsOrder.length; i < j; i++) {
if (methodsOrder[i] === 'static-methods') {
indexes.push(i);
break;
}
}
}
if (method.typeAnnotation) {
for (i = 0, j = methodsOrder.length; i < j; i++) {
if (methodsOrder[i] === 'type-annotations') {
indexes.push(i);
break;
}
}
}
// Either this is not a static method or static methods are not specified
// in the methodsOrder.
if (indexes.length === 0) {
for (i = 0, j = methodsOrder.length; i < j; i++) {
isRegExp = methodsOrder[i].match(regExpRegExp);
if (isRegExp) {
matching = new RegExp(isRegExp[1], isRegExp[2]).test(method.name);
} else {
matching = methodsOrder[i] === method.name;
}
if (matching) {
indexes.push(i);
}
}
}
// No matching pattern, return 'everything-else' index
if (indexes.length === 0) {
for (i = 0, j = methodsOrder.length; i < j; i++) {
if (methodsOrder[i] === 'everything-else') {
indexes.push(i);
break;
}
}
}
// No matching pattern and no 'everything-else' group
if (indexes.length === 0) {
indexes.push(Infinity);
}
return indexes;
}
/**
* Get properties name
* @param {Object} node - Property.
* @returns {String} Property name.
*/
function getPropertyName(node) {
// Special case for class properties
// (babel-eslint does not expose property name so we have to rely on tokens)
if (node.type === 'ClassProperty') {
var tokens = context.getFirstTokens(node, 2);
return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value;
}
return node.key.name;
}
/**
* Store a new error in the error list
* @param {Object} propA - Mispositioned property.
* @param {Object} propB - Reference property.
*/
function storeError(propA, propB) {
// Initialize the error object if needed
if (!errors[propA.index]) {
errors[propA.index] = {
node: propA.node,
score: 0,
closest: {
distance: Infinity,
ref: {
node: null,
index: 0
}
}
};
}
// Increment the prop score
errors[propA.index].score++;
// Stop here if we already have pushed another node at this position
if (getPropertyName(errors[propA.index].node) !== getPropertyName(propA.node)) {
return;
}
// Stop here if we already have a closer reference
if (Math.abs(propA.index - propB.index) > errors[propA.index].closest.distance) {
return;
}
// Update the closest reference
errors[propA.index].closest.distance = Math.abs(propA.index - propB.index);
errors[propA.index].closest.ref.node = propB.node;
errors[propA.index].closest.ref.index = propB.index;
}
/**
* Dedupe errors, only keep the ones with the highest score and delete the others
*/
function dedupeErrors() {
for (var i in errors) {
if (!has(errors, i)) {
continue;
}
var index = errors[i].closest.ref.index;
if (!errors[index]) {
continue;
}
if (errors[i].score > errors[index].score) {
delete errors[index];
} else {
delete errors[i];
}
}
}
/**
* Report errors
*/
function reportErrors() {
dedupeErrors();
var nodeA;
var nodeB;
var indexA;
var indexB;
for (var i in errors) {
if (!has(errors, i)) {
continue;
}
nodeA = errors[i].node;
nodeB = errors[i].closest.ref.node;
indexA = i;
indexB = errors[i].closest.ref.index;
context.report({
node: nodeA,
message: MISPOSITION_MESSAGE,
data: {
propA: getPropertyName(nodeA),
propB: getPropertyName(nodeB),
position: indexA < indexB ? 'before' : 'after'
}
});
}
}
/**
* Get properties for a given AST node
* @param {ASTNode} node The AST node being checked.
* @returns {Array} Properties array.
*/
function getComponentProperties(node) {
switch (node.type) {
case 'ClassDeclaration':
return node.body.body;
case 'ObjectExpression':
return node.properties.filter(function(property) {
return property.type === 'Property';
});
default:
return [];
}
}
/**
* Compare two properties and find out if they are in the right order
* @param {Array} propertiesInfos Array containing all the properties metadata.
* @param {Object} propA First property name and metadata
* @param {Object} propB Second property name.
* @returns {Object} Object containing a correct true/false flag and the correct indexes for the two properties.
*/
function comparePropsOrder(propertiesInfos, propA, propB) {
var i;
var j;
var k;
var l;
var refIndexA;
var refIndexB;
// Get references indexes (the correct position) for given properties
var refIndexesA = getRefPropIndexes(propA);
var refIndexesB = getRefPropIndexes(propB);
// Get current indexes for given properties
var classIndexA = propertiesInfos.indexOf(propA);
var classIndexB = propertiesInfos.indexOf(propB);
// Loop around the references indexes for the 1st property
for (i = 0, j = refIndexesA.length; i < j; i++) {
refIndexA = refIndexesA[i];
// Loop around the properties for the 2nd property (for comparison)
for (k = 0, l = refIndexesB.length; k < l; k++) {
refIndexB = refIndexesB[k];
if (
// Comparing the same properties
refIndexA === refIndexB ||
// 1st property is placed before the 2nd one in reference and in current component
refIndexA < refIndexB && classIndexA < classIndexB ||
// 1st property is placed after the 2nd one in reference and in current component
refIndexA > refIndexB && classIndexA > classIndexB
) {
return {
correct: true,
indexA: classIndexA,
indexB: classIndexB
};
}
}
}
// We did not find any correct match between reference and current component
return {
correct: false,
indexA: refIndexA,
indexB: refIndexB
};
}
/**
* Check properties order from a properties list and store the eventual errors
* @param {Array} properties Array containing all the properties.
*/
function checkPropsOrder(properties) {
var propertiesInfos = properties.map(function(node) {
return {
name: getPropertyName(node),
static: node.static,
typeAnnotation: !!node.typeAnnotation && node.value === null
};
});
var i;
var j;
var k;
var l;
var propA;
var propB;
var order;
// Loop around the properties
for (i = 0, j = propertiesInfos.length; i < j; i++) {
propA = propertiesInfos[i];
// Loop around the properties a second time (for comparison)
for (k = 0, l = propertiesInfos.length; k < l; k++) {
propB = propertiesInfos[k];
// Compare the properties order
order = comparePropsOrder(propertiesInfos, propA, propB);
// Continue to next comparison is order is correct
if (order.correct === true) {
continue;
}
// Store an error if the order is incorrect
storeError({
node: properties[i],
index: order.indexA
}, {
node: properties[k],
index: order.indexB
});
}
}
}
return {
'Program:exit': function() {
var list = components.list();
for (var component in list) {
if (!has(list, component)) {
continue;
}
var properties = getComponentProperties(list[component].node);
checkPropsOrder(properties);
}
reportErrors();
}
};
})
};

View File

@@ -0,0 +1,201 @@
/**
* @fileoverview Enforce propTypes declarations alphabetical sorting
*/
'use strict';
var variableUtil = require('../util/variable');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Enforce propTypes declarations alphabetical sorting',
category: 'Stylistic Issues',
recommended: false
},
schema: [{
type: 'object',
properties: {
requiredFirst: {
type: 'boolean'
},
callbacksLast: {
type: 'boolean'
},
ignoreCase: {
type: 'boolean'
}
},
additionalProperties: false
}]
},
create: function(context) {
var sourceCode = context.getSourceCode();
var configuration = context.options[0] || {};
var requiredFirst = configuration.requiredFirst || false;
var callbacksLast = configuration.callbacksLast || false;
var ignoreCase = configuration.ignoreCase || false;
/**
* Checks if node is `propTypes` declaration
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if node is `propTypes` declaration, false if not.
*/
function isPropTypesDeclaration(node) {
// Special case for class properties
// (babel-eslint does not expose property name so we have to rely on tokens)
if (node.type === 'ClassProperty') {
var tokens = context.getFirstTokens(node, 2);
return (tokens[0] && tokens[0].value === 'propTypes') ||
(tokens[1] && tokens[1].value === 'propTypes');
}
return Boolean(
node &&
node.name === 'propTypes'
);
}
function getKey(node) {
return sourceCode.getText(node.key || node.argument);
}
function getValueName(node) {
return node.type === 'Property' && node.value.property && node.value.property.name;
}
function isCallbackPropName(propName) {
return /^on[A-Z]/.test(propName);
}
function isRequiredProp(node) {
return getValueName(node) === 'isRequired';
}
/**
* Checks if propTypes declarations are sorted
* @param {Array} declarations The array of AST nodes being checked.
* @returns {void}
*/
function checkSorted(declarations) {
declarations.reduce(function(prev, curr, idx, decls) {
if (/SpreadProperty$/.test(curr.type)) {
return decls[idx + 1];
}
var prevPropName = getKey(prev);
var currentPropName = getKey(curr);
var previousIsRequired = isRequiredProp(prev);
var currentIsRequired = isRequiredProp(curr);
var previousIsCallback = isCallbackPropName(prevPropName);
var currentIsCallback = isCallbackPropName(currentPropName);
if (ignoreCase) {
prevPropName = prevPropName.toLowerCase();
currentPropName = currentPropName.toLowerCase();
}
if (requiredFirst) {
if (previousIsRequired && !currentIsRequired) {
// Transition between required and non-required. Don't compare for alphabetical.
return curr;
}
if (!previousIsRequired && currentIsRequired) {
// Encountered a non-required prop after a required prop
context.report({
node: curr,
message: 'Required prop types must be listed before all other prop types'
});
return curr;
}
}
if (callbacksLast) {
if (!previousIsCallback && currentIsCallback) {
// Entering the callback prop section
return curr;
}
if (previousIsCallback && !currentIsCallback) {
// Encountered a non-callback prop after a callback prop
context.report({
node: prev,
message: 'Callback prop types must be listed after all other prop types'
});
return prev;
}
}
if (currentPropName < prevPropName) {
context.report({
node: curr,
message: 'Prop types declarations should be sorted alphabetically'
});
return prev;
}
return curr;
}, declarations[0]);
}
return {
ClassProperty: function(node) {
if (isPropTypesDeclaration(node) && node.value && node.value.type === 'ObjectExpression') {
checkSorted(node.value.properties);
}
},
MemberExpression: function(node) {
if (!isPropTypesDeclaration(node.property)) {
return;
}
var right = node.parent.right;
var declarations;
switch (right && right.type) {
case 'ObjectExpression':
declarations = right.properties;
break;
case 'Identifier':
var variable = variableUtil.variablesInScope(context).find(function (item) {
return item.name === right.name;
});
if (
!variable || !variable.defs[0] ||
!variable.defs[0].node.init || !variable.defs[0].node.init.properties
) {
break;
}
declarations = variable.defs[0].node.init.properties;
break;
default:
break;
}
if (declarations) {
checkSorted(declarations);
}
},
ObjectExpression: function(node) {
node.properties.forEach(function(property) {
if (!property.key) {
return;
}
if (!isPropTypesDeclaration(property.key)) {
return;
}
if (property.value.type === 'ObjectExpression') {
checkSorted(property.value.properties);
}
});
}
};
}
};

View File

@@ -0,0 +1,84 @@
/**
* @fileoverview Enforce style prop value is an object
* @author David Petersen
*/
'use strict';
var variableUtil = require('../util/variable');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Enforce style prop value is an object',
category: '',
recommended: false
},
schema: []
},
create: function(context) {
/**
* @param {object} node An Identifier node
*/
function isNonNullaryLiteral(expression) {
return expression.type === 'Literal' && expression.value !== null;
}
/**
* @param {object} node A Identifier node
*/
function checkIdentifiers(node) {
var variable = variableUtil.variablesInScope(context).find(function (item) {
return item.name === node.name;
});
if (!variable || !variable.defs[0] || !variable.defs[0].node.init) {
return;
}
if (isNonNullaryLiteral(variable.defs[0].node.init)) {
context.report(node, 'Style prop value must be an object');
}
}
return {
CallExpression: function(node) {
if (
node.callee
&& node.callee.type === 'MemberExpression'
&& node.callee.property.name === 'createElement'
&& node.arguments.length > 1
) {
if (node.arguments[1].type === 'ObjectExpression') {
var style = node.arguments[1].properties.find(function(property) {
return property.key && property.key.name === 'style' && !property.computed;
});
if (style) {
if (style.value.type === 'Identifier') {
checkIdentifiers(style.value);
} else if (isNonNullaryLiteral(style.value)) {
context.report(style.value, 'Style prop value must be an object');
}
}
}
}
},
JSXAttribute: function(node) {
if (!node.value || node.name.name !== 'style') {
return;
}
if (node.value.type !== 'JSXExpressionContainer' || isNonNullaryLiteral(node.value.expression)) {
context.report(node, 'Style prop value must be an object');
} else if (node.value.expression.type === 'Identifier') {
checkIdentifiers(node.value.expression);
}
}
};
}
};

View File

@@ -0,0 +1,146 @@
/**
* @fileoverview Prevent void elements (e.g. <img />, <br />) from receiving
* children
* @author Joe Lencioni
*/
'use strict';
var has = require('has');
var Components = require('../util/Components');
// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------
// Using an object here to avoid array scan. We should switch to Set once
// support is good enough.
var VOID_DOM_ELEMENTS = {
area: true,
base: true,
br: true,
col: true,
embed: true,
hr: true,
img: true,
input: true,
keygen: true,
link: true,
menuitem: true,
meta: true,
param: true,
source: true,
track: true,
wbr: true
};
function isVoidDOMElement(elementName) {
return has(VOID_DOM_ELEMENTS, elementName);
}
function errorMessage(elementName) {
return `Void DOM element <${elementName} /> cannot receive children.`;
}
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent passing of children to void DOM elements (e.g. <br />).',
category: 'Best Practices',
recommended: false
},
schema: []
},
create: Components.detect(function(context, components, utils) {
return {
JSXElement: function(node) {
var elementName = node.openingElement.name.name;
if (!isVoidDOMElement(elementName)) {
// e.g. <div />
return;
}
if (node.children.length > 0) {
// e.g. <br>Foo</br>
context.report({
node: node,
message: errorMessage(elementName)
});
}
var attributes = node.openingElement.attributes;
var hasChildrenAttributeOrDanger = attributes.some(function(attribute) {
if (!attribute.name) {
return false;
}
return attribute.name.name === 'children' || attribute.name.name === 'dangerouslySetInnerHTML';
});
if (hasChildrenAttributeOrDanger) {
// e.g. <br children="Foo" />
context.report({
node: node,
message: errorMessage(elementName)
});
}
},
CallExpression: function(node) {
if (node.callee.type !== 'MemberExpression' && node.callee.type !== 'Identifier') {
return;
}
if (!utils.isReactCreateElement(node)) {
return;
}
var args = node.arguments;
var elementName = args[0].value;
if (!isVoidDOMElement(elementName)) {
// e.g. React.createElement('div');
return;
}
if (args.length < 2) {
return;
}
var firstChild = args[2];
if (firstChild) {
// e.g. React.createElement('br', undefined, 'Foo')
context.report({
node: node,
message: errorMessage(elementName)
});
}
var props = args[1].properties;
var hasChildrenPropOrDanger = props.some(function(prop) {
if (!prop.key) {
return false;
}
return prop.key.name === 'children' || prop.key.name === 'dangerouslySetInnerHTML';
});
if (hasChildrenPropOrDanger) {
// e.g. React.createElement('br', { children: 'Foo' })
context.report({
node: node,
message: errorMessage(elementName)
});
}
}
};
})
};

632
web/node_modules/eslint-plugin-react/lib/util/Components.js generated vendored Executable file
View File

@@ -0,0 +1,632 @@
/**
* @fileoverview Utility class and functions for React components detection
* @author Yannick Croissant
*/
'use strict';
var has = require('has');
var util = require('util');
var doctrine = require('doctrine');
var variableUtil = require('./variable');
var pragmaUtil = require('./pragma');
/**
* Components
* @class
*/
function Components() {
this._list = {};
this._getId = function(node) {
return node && node.range.join(':');
};
}
/**
* Add a node to the components list, or update it if it's already in the list
*
* @param {ASTNode} node The AST node being added.
* @param {Number} confidence Confidence in the component detection (0=banned, 1=maybe, 2=yes)
* @returns {Object} Added component object
*/
Components.prototype.add = function(node, confidence) {
var id = this._getId(node);
if (this._list[id]) {
if (confidence === 0 || this._list[id].confidence === 0) {
this._list[id].confidence = 0;
} else {
this._list[id].confidence = Math.max(this._list[id].confidence, confidence);
}
return this._list[id];
}
this._list[id] = {
node: node,
confidence: confidence
};
return this._list[id];
};
/**
* Find a component in the list using its node
*
* @param {ASTNode} node The AST node being searched.
* @returns {Object} Component object, undefined if the component is not found
*/
Components.prototype.get = function(node) {
var id = this._getId(node);
return this._list[id];
};
/**
* Update a component in the list
*
* @param {ASTNode} node The AST node being updated.
* @param {Object} props Additional properties to add to the component.
*/
Components.prototype.set = function(node, props) {
while (node && !this._list[this._getId(node)]) {
node = node.parent;
}
if (!node) {
return;
}
var id = this._getId(node);
this._list[id] = util._extend(this._list[id], props);
};
/**
* Return the components list
* Components for which we are not confident are not returned
*
* @returns {Object} Components list
*/
Components.prototype.list = function() {
var list = {};
var usedPropTypes = {};
// Find props used in components for which we are not confident
for (var i in this._list) {
if (!has(this._list, i) || this._list[i].confidence >= 2) {
continue;
}
var component = null;
var node = null;
node = this._list[i].node;
while (!component && node.parent) {
node = node.parent;
// Stop moving up if we reach a decorator
if (node.type === 'Decorator') {
break;
}
component = this.get(node);
}
if (component) {
usedPropTypes[this._getId(component.node)] = (this._list[i].usedPropTypes || []).filter(function(propType) {
return !propType.node || propType.node.kind !== 'init';
});
}
}
// Assign used props in not confident components to the parent component
for (var j in this._list) {
if (!has(this._list, j) || this._list[j].confidence < 2) {
continue;
}
var id = this._getId(this._list[j].node);
list[j] = this._list[j];
if (usedPropTypes[id]) {
list[j].usedPropTypes = (list[j].usedPropTypes || []).concat(usedPropTypes[id]);
}
}
return list;
};
/**
* Return the length of the components list
* Components for which we are not confident are not counted
*
* @returns {Number} Components list length
*/
Components.prototype.length = function() {
var length = 0;
for (var i in this._list) {
if (!has(this._list, i) || this._list[i].confidence < 2) {
continue;
}
length++;
}
return length;
};
function componentRule(rule, context) {
var createClass = pragmaUtil.getCreateClassFromContext(context);
var pragma = pragmaUtil.getFromContext(context);
var sourceCode = context.getSourceCode();
var components = new Components();
// Utilities for component detection
var utils = {
/**
* Check if the node is a React ES5 component
*
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if the node is a React ES5 component, false if not
*/
isES5Component: function(node) {
if (!node.parent) {
return false;
}
return new RegExp(`^(${pragma}\\.)?${createClass}$`).test(sourceCode.getText(node.parent.callee));
},
/**
* Check if the node is a React ES6 component
*
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if the node is a React ES6 component, false if not
*/
isES6Component: function(node) {
if (utils.isExplicitComponent(node)) {
return true;
}
if (!node.superClass) {
return false;
}
return new RegExp(`^(${pragma}\\.)?(Pure)?Component$`).test(sourceCode.getText(node.superClass));
},
/**
* Check if the node is explicitly declared as a descendant of a React Component
*
* @param {ASTNode} node The AST node being checked (can be a ReturnStatement or an ArrowFunctionExpression).
* @returns {Boolean} True if the node is explicitly declared as a descendant of a React Component, false if not
*/
isExplicitComponent: function(node) {
var comment = sourceCode.getJSDocComment(node);
if (comment === null) {
return false;
}
var commentAst = doctrine.parse(comment.value, {
unwrap: true,
tags: ['extends', 'augments']
});
var relevantTags = commentAst.tags.filter(function(tag) {
return tag.name === 'React.Component' || tag.name === 'React.PureComponent';
});
return relevantTags.length > 0;
},
/**
* Checks to see if our component extends React.PureComponent
*
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if node extends React.PureComponent, false if not
*/
isPureComponent: function (node) {
if (node.superClass) {
return new RegExp(`^(${pragma}\\.)?PureComponent$`).test(sourceCode.getText(node.superClass));
}
return false;
},
/**
* Check if createElement is destructured from React import
*
* @returns {Boolean} True if createElement is destructured from React
*/
hasDestructuredReactCreateElement: function() {
var variables = variableUtil.variablesInScope(context);
var variable = variableUtil.getVariable(variables, 'createElement');
if (variable) {
var map = variable.scope.set;
if (map.has('React')) {
return true;
}
}
return false;
},
/**
* Checks to see if node is called within React.createElement
*
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if React.createElement called
*/
isReactCreateElement: function(node) {
var calledOnReact = (
node &&
node.callee &&
node.callee.object &&
node.callee.object.name === 'React' &&
node.callee.property &&
node.callee.property.name === 'createElement'
);
var calledDirectly = (
node &&
node.callee &&
node.callee.name === 'createElement'
);
if (this.hasDestructuredReactCreateElement()) {
return calledDirectly || calledOnReact;
}
return calledOnReact;
},
/**
* Check if the node is returning JSX
*
* @param {ASTNode} ASTnode The AST node being checked
* @param {Boolean} strict If true, in a ternary condition the node must return JSX in both cases
* @returns {Boolean} True if the node is returning JSX, false if not
*/
isReturningJSX: function(ASTnode, strict) {
var property;
var node = ASTnode;
switch (node.type) {
case 'ReturnStatement':
property = 'argument';
break;
case 'ArrowFunctionExpression':
property = 'body';
break;
default:
node = utils.findReturnStatement(node);
if (!node) {
return false;
}
property = 'argument';
}
var returnsConditionalJSXConsequent =
node[property] &&
node[property].type === 'ConditionalExpression' &&
node[property].consequent.type === 'JSXElement'
;
var returnsConditionalJSXAlternate =
node[property] &&
node[property].type === 'ConditionalExpression' &&
node[property].alternate.type === 'JSXElement'
;
var returnsConditionalJSX =
strict ?
(returnsConditionalJSXConsequent && returnsConditionalJSXAlternate) :
(returnsConditionalJSXConsequent || returnsConditionalJSXAlternate);
var returnsJSX =
node[property] &&
node[property].type === 'JSXElement'
;
var returnsReactCreateElement = this.isReactCreateElement(node[property]);
return Boolean(
returnsConditionalJSX ||
returnsJSX ||
returnsReactCreateElement
);
},
/**
* Find a return statment in the current node
*
* @param {ASTNode} ASTnode The AST node being checked
*/
findReturnStatement: function(node) {
if (!node.value || !node.value.body || !node.value.body.body) {
return false;
}
var i = node.value.body.body.length - 1;
for (; i >= 0; i--) {
if (node.value.body.body[i].type === 'ReturnStatement') {
return node.value.body.body[i];
}
}
return false;
},
/**
* Get the parent component node from the current scope
*
* @returns {ASTNode} component node, null if we are not in a component
*/
getParentComponent: function() {
return (
utils.getParentES6Component() ||
utils.getParentES5Component() ||
utils.getParentStatelessComponent()
);
},
/**
* Get the parent ES5 component node from the current scope
*
* @returns {ASTNode} component node, null if we are not in a component
*/
getParentES5Component: function() {
var scope = context.getScope();
while (scope) {
var node = scope.block && scope.block.parent && scope.block.parent.parent;
if (node && utils.isES5Component(node)) {
return node;
}
scope = scope.upper;
}
return null;
},
/**
* Get the parent ES6 component node from the current scope
*
* @returns {ASTNode} component node, null if we are not in a component
*/
getParentES6Component: function() {
var scope = context.getScope();
while (scope && scope.type !== 'class') {
scope = scope.upper;
}
var node = scope && scope.block;
if (!node || !utils.isES6Component(node)) {
return null;
}
return node;
},
/**
* Get the parent stateless component node from the current scope
*
* @returns {ASTNode} component node, null if we are not in a component
*/
getParentStatelessComponent: function() {
var scope = context.getScope();
while (scope) {
var node = scope.block;
var isClass = node.type === 'ClassExpression';
var isFunction = /Function/.test(node.type); // Functions
var isMethod = node.parent && node.parent.type === 'MethodDefinition'; // Classes methods
var isArgument = node.parent && node.parent.type === 'CallExpression'; // Arguments (callback, etc.)
// Attribute Expressions inside JSX Elements (<button onClick={() => props.handleClick()}></button>)
var isJSXExpressionContainer = node.parent && node.parent.type === 'JSXExpressionContainer';
// Stop moving up if we reach a class or an argument (like a callback)
if (isClass || isArgument) {
return null;
}
// Return the node if it is a function that is not a class method and is not inside a JSX Element
if (isFunction && !isMethod && !isJSXExpressionContainer) {
return node;
}
scope = scope.upper;
}
return null;
},
/**
* Get the related component from a node
*
* @param {ASTNode} node The AST node being checked (must be a MemberExpression).
* @returns {ASTNode} component node, null if we cannot find the component
*/
getRelatedComponent: function(node) {
var i;
var j;
var k;
var l;
var componentName;
var componentNode;
// Get the component path
var componentPath = [];
while (node) {
if (node.property && node.property.type === 'Identifier') {
componentPath.push(node.property.name);
}
if (node.object && node.object.type === 'Identifier') {
componentPath.push(node.object.name);
}
node = node.object;
}
componentPath.reverse();
componentName = componentPath.slice(0, componentPath.length - 1).join('.');
// Find the variable in the current scope
var variableName = componentPath.shift();
if (!variableName) {
return null;
}
var variableInScope;
var variables = variableUtil.variablesInScope(context);
for (i = 0, j = variables.length; i < j; i++) {
if (variables[i].name === variableName) {
variableInScope = variables[i];
break;
}
}
if (!variableInScope) {
return null;
}
// Try to find the component using variable references
var refs = variableInScope.references;
var refId;
for (i = 0, j = refs.length; i < j; i++) {
refId = refs[i].identifier;
if (refId.parent && refId.parent.type === 'MemberExpression') {
refId = refId.parent;
}
if (sourceCode.getText(refId) !== componentName) {
continue;
}
if (refId.type === 'MemberExpression') {
componentNode = refId.parent.right;
} else if (refId.parent && refId.parent.type === 'VariableDeclarator') {
componentNode = refId.parent.init;
}
break;
}
if (componentNode) {
// Return the component
return components.add(componentNode, 1);
}
// Try to find the component using variable declarations
var defInScope;
var defs = variableInScope.defs;
for (i = 0, j = defs.length; i < j; i++) {
if (defs[i].type === 'ClassName' || defs[i].type === 'FunctionName' || defs[i].type === 'Variable') {
defInScope = defs[i];
break;
}
}
if (!defInScope || !defInScope.node) {
return null;
}
componentNode = defInScope.node.init || defInScope.node;
// Traverse the node properties to the component declaration
for (i = 0, j = componentPath.length; i < j; i++) {
if (!componentNode.properties) {
continue;
}
for (k = 0, l = componentNode.properties.length; k < l; k++) {
if (componentNode.properties[k].key.name === componentPath[i]) {
componentNode = componentNode.properties[k];
break;
}
}
if (!componentNode || !componentNode.value) {
return null;
}
componentNode = componentNode.value;
}
// Return the component
return components.add(componentNode, 1);
}
};
// Component detection instructions
var detectionInstructions = {
ClassExpression: function(node) {
if (!utils.isES6Component(node)) {
return;
}
components.add(node, 2);
},
ClassDeclaration: function(node) {
if (!utils.isES6Component(node)) {
return;
}
components.add(node, 2);
},
ClassProperty: function(node) {
node = utils.getParentComponent();
if (!node) {
return;
}
components.add(node, 2);
},
ObjectExpression: function(node) {
if (!utils.isES5Component(node)) {
return;
}
components.add(node, 2);
},
FunctionExpression: function(node) {
if (node.async) {
components.add(node, 0);
return;
}
var component = utils.getParentComponent();
if (
!component ||
(component.parent && component.parent.type === 'JSXExpressionContainer')
) {
// Ban the node if we cannot find a parent component
components.add(node, 0);
return;
}
components.add(component, 1);
},
FunctionDeclaration: function(node) {
if (node.async) {
components.add(node, 0);
return;
}
node = utils.getParentComponent();
if (!node) {
return;
}
components.add(node, 1);
},
ArrowFunctionExpression: function(node) {
if (node.async) {
components.add(node, 0);
return;
}
var component = utils.getParentComponent();
if (
!component ||
(component.parent && component.parent.type === 'JSXExpressionContainer')
) {
// Ban the node if we cannot find a parent component
components.add(node, 0);
return;
}
if (component.expression && utils.isReturningJSX(component)) {
components.add(component, 2);
} else {
components.add(component, 1);
}
},
ThisExpression: function(node) {
var component = utils.getParentComponent();
if (!component || !/Function/.test(component.type) || !node.parent.property) {
return;
}
// Ban functions accessing a property on a ThisExpression
components.add(node, 0);
},
ReturnStatement: function(node) {
if (!utils.isReturningJSX(node)) {
return;
}
node = utils.getParentComponent();
if (!node) {
var scope = context.getScope();
components.add(scope.block, 1);
return;
}
components.add(node, 2);
}
};
// Update the provided rule instructions to add the component detection
var ruleInstructions = rule(context, components, utils);
var updatedRuleInstructions = util._extend({}, ruleInstructions);
Object.keys(detectionInstructions).forEach(function(instruction) {
updatedRuleInstructions[instruction] = function(node) {
detectionInstructions[instruction](node);
return ruleInstructions[instruction] ? ruleInstructions[instruction](node) : void 0;
};
});
// Return the updated rule instructions
return updatedRuleInstructions;
}
Components.detect = function(rule) {
return componentRule.bind(this, rule);
};
module.exports = Components;

28
web/node_modules/eslint-plugin-react/lib/util/annotations.js generated vendored Executable file
View File

@@ -0,0 +1,28 @@
/**
* @fileoverview Utility functions for type annotation detection.
* @author Yannick Croissant
* @author Vitor Balocco
*/
'use strict';
/**
* Checks if we are declaring a `props` argument with a flow type annotation.
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if the node is a type annotated props declaration, false if not.
*/
function isAnnotatedFunctionPropsDeclaration(node, context) {
if (!node || !node.params || !node.params.length) {
return false;
}
var tokens = context.getFirstTokens(node.params[0], 2);
var isAnnotated = node.params[0].typeAnnotation;
var isDestructuredProps = node.params[0].type === 'ObjectPattern';
var isProps = tokens[0].value === 'props' || (tokens[1] && tokens[1].value === 'props');
return (isAnnotated && (isDestructuredProps || isProps));
}
module.exports = {
isAnnotatedFunctionPropsDeclaration: isAnnotatedFunctionPropsDeclaration
};

View File

@@ -0,0 +1,16 @@
'use strict';
/**
* Find the token before the closing bracket.
* @param {ASTNode} node - The JSX element node.
* @returns {Token} The token before the closing bracket.
*/
function getTokenBeforeClosingBracket(node) {
var attributes = node.attributes;
if (attributes.length === 0) {
return node.name;
}
return attributes[attributes.length - 1];
}
module.exports = getTokenBeforeClosingBracket;

View File

@@ -0,0 +1,70 @@
/**
* @fileoverview Prevent usage of setState in lifecycle methods
* @author Yannick Croissant
*/
'use strict';
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
function makeNoMethodSetStateRule(methodName) {
return {
meta: {
docs: {
description: `Prevent usage of setState in ${methodName}`,
category: 'Best Practices',
recommended: false
},
schema: [{
enum: ['disallow-in-func']
}]
},
create: function(context) {
var mode = context.options[0] || 'allow-in-func';
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
CallExpression: function(node) {
var callee = node.callee;
if (
callee.type !== 'MemberExpression' ||
callee.object.type !== 'ThisExpression' ||
callee.property.name !== 'setState'
) {
return;
}
var ancestors = context.getAncestors(callee).reverse();
var depth = 0;
for (var i = 0, j = ancestors.length; i < j; i++) {
if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) {
depth++;
}
if (
(ancestors[i].type !== 'Property' && ancestors[i].type !== 'MethodDefinition') ||
ancestors[i].key.name !== methodName ||
(mode !== 'disallow-in-func' && depth > 1)
) {
continue;
}
context.report({
node: callee,
message: `Do not use setState in ${methodName}`
});
break;
}
}
};
}
};
}
module.exports = makeNoMethodSetStateRule;

49
web/node_modules/eslint-plugin-react/lib/util/pragma.js generated vendored Executable file
View File

@@ -0,0 +1,49 @@
/**
* @fileoverview Utility functions for React pragma configuration
* @author Yannick Croissant
*/
'use strict';
var JSX_ANNOTATION_REGEX = /^\*\s*@jsx\s+([^\s]+)/;
// Does not check for reserved keywords or unicode characters
var JS_IDENTIFIER_REGEX = /^[_$a-zA-Z][_$a-zA-Z0-9]*$/;
function getCreateClassFromContext(context) {
var pragma = 'createReactClass';
// .eslintrc shared settings (http://eslint.org/docs/user-guide/configuring#adding-shared-settings)
if (context.settings.react && context.settings.react.createClass) {
pragma = context.settings.react.createClass;
}
if (!JS_IDENTIFIER_REGEX.test(pragma)) {
throw new Error(`createClass pragma ${pragma} is not a valid function name`);
}
return pragma;
}
function getFromContext(context) {
var pragma = 'React';
var sourceCode = context.getSourceCode();
var pragmaNode = sourceCode.getAllComments().find(function(node) {
return JSX_ANNOTATION_REGEX.test(node.value);
});
if (pragmaNode) {
var matches = JSX_ANNOTATION_REGEX.exec(pragmaNode.value);
pragma = matches[1].split('.')[0];
// .eslintrc shared settings (http://eslint.org/docs/user-guide/configuring#adding-shared-settings)
} else if (context.settings.react && context.settings.react.pragma) {
pragma = context.settings.react.pragma;
}
if (!JS_IDENTIFIER_REGEX.test(pragma)) {
throw new Error(`React pragma ${pragma} is not a valid identifier`);
}
return pragma;
}
module.exports = {
getCreateClassFromContext: getCreateClassFromContext,
getFromContext: getFromContext
};

62
web/node_modules/eslint-plugin-react/lib/util/variable.js generated vendored Executable file
View File

@@ -0,0 +1,62 @@
/**
* @fileoverview Utility functions for React components detection
* @author Yannick Croissant
*/
'use strict';
/**
* Search a particular variable in a list
* @param {Array} variables The variables list.
* @param {Array} name The name of the variable to search.
* @returns {Boolean} True if the variable was found, false if not.
*/
function findVariable(variables, name) {
return variables.some(function (variable) {
return variable.name === name;
});
}
/**
* Find and return a particular variable in a list
* @param {Array} variables The variables list.
* @param {Array} name The name of the variable to search.
* @returns {Object} Variable if the variable was found, null if not.
*/
function getVariable(variables, name) {
return variables.find(function (variable) {
return variable.name === name;
});
}
/**
* List all variable in a given scope
*
* Contain a patch for babel-eslint to avoid https://github.com/babel/babel-eslint/issues/21
*
* @param {Object} context The current rule context.
* @returns {Array} The variables list
*/
function variablesInScope(context) {
var scope = context.getScope();
var variables = scope.variables;
while (scope.type !== 'global') {
scope = scope.upper;
variables = scope.variables.concat(variables);
}
if (scope.childScopes.length) {
variables = scope.childScopes[0].variables.concat(variables);
if (scope.childScopes[0].childScopes.length) {
variables = scope.childScopes[0].childScopes[0].variables.concat(variables);
}
}
variables.reverse();
return variables;
}
module.exports = {
findVariable: findVariable,
getVariable: getVariable,
variablesInScope: variablesInScope
};

33
web/node_modules/eslint-plugin-react/lib/util/version.js generated vendored Executable file
View File

@@ -0,0 +1,33 @@
/**
* @fileoverview Utility functions for React version configuration
* @author Yannick Croissant
*/
'use strict';
function getFromContext(context) {
var confVer = '999.999.999';
// .eslintrc shared settings (http://eslint.org/docs/user-guide/configuring#adding-shared-settings)
if (context.settings.react && context.settings.react.version) {
confVer = context.settings.react.version;
}
confVer = /^[0-9]+\.[0-9]+$/.test(confVer) ? `${confVer}.0` : confVer;
return confVer.split('.').map(function(part) {
return Number(part);
});
}
function test(context, methodVer) {
var confVer = getFromContext(context);
methodVer = methodVer.split('.').map(function(part) {
return Number(part);
});
var higherMajor = methodVer[0] < confVer[0];
var higherMinor = methodVer[0] === confVer[0] && methodVer[1] < confVer[1];
var higherOrEqualPatch = methodVer[0] === confVer[0] && methodVer[1] === confVer[1] && methodVer[2] <= confVer[2];
return higherMajor || higherMinor || higherOrEqualPatch;
}
module.exports = {
test: test
};

80
web/node_modules/eslint-plugin-react/package.json generated vendored Executable file
View File

@@ -0,0 +1,80 @@
{
"_from": "eslint-plugin-react@7.0.1",
"_id": "eslint-plugin-react@7.0.1",
"_inBundle": false,
"_integrity": "sha1-54EH4eVZxuKxd4a7Z8LioBCtDS8=",
"_location": "/eslint-plugin-react",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "eslint-plugin-react@7.0.1",
"name": "eslint-plugin-react",
"escapedName": "eslint-plugin-react",
"rawSpec": "7.0.1",
"saveSpec": null,
"fetchSpec": "7.0.1"
},
"_requiredBy": [
"/react-scripts"
],
"_resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.0.1.tgz",
"_shasum": "e78107e1e559c6e2b17786bb67c2e2a010ad0d2f",
"_spec": "eslint-plugin-react@7.0.1",
"_where": "/home/bilal/Saburly/slucajna-televizija/node_modules/react-scripts",
"author": {
"name": "Yannick Croissant",
"email": "yannick.croissant+npm@gmail.com"
},
"bugs": {
"url": "https://github.com/yannickcr/eslint-plugin-react/issues"
},
"bundleDependencies": false,
"dependencies": {
"doctrine": "^2.0.0",
"has": "^1.0.1",
"jsx-ast-utils": "^1.3.4"
},
"deprecated": false,
"description": "React specific linting rules for ESLint",
"devDependencies": {
"babel-eslint": "7.2.3",
"coveralls": "2.13.1",
"eslint": "^3.0.0",
"istanbul": "0.4.5",
"mocha": "3.3.0"
},
"engines": {
"node": ">=4"
},
"files": [
"LICENSE",
"README.md",
"index.js",
"lib"
],
"homepage": "https://github.com/yannickcr/eslint-plugin-react",
"keywords": [
"eslint",
"eslint-plugin",
"eslintplugin",
"react"
],
"license": "MIT",
"main": "index.js",
"name": "eslint-plugin-react",
"peerDependencies": {
"eslint": "^3.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/yannickcr/eslint-plugin-react.git"
},
"scripts": {
"coveralls": "cat ./reports/coverage/lcov.info | coveralls",
"lint": "eslint ./",
"test": "npm run lint && npm run unit-test",
"unit-test": "istanbul cover --dir reports/coverage node_modules/mocha/bin/_mocha tests/**/*.js -- --reporter dot"
},
"version": "7.0.1"
}