eslint-plugin-react
Advanced tools
Comparing version 7.31.10 to 7.32.2
198
index.js
'use strict'; | ||
const fromEntries = require('object.fromentries'); | ||
const entries = require('object.entries'); | ||
const configAll = require('./configs/all'); | ||
const configRecommended = require('./configs/recommended'); | ||
const configRuntime = require('./configs/jsx-runtime'); | ||
/* eslint-disable global-require */ | ||
const allRules = { | ||
'boolean-prop-naming': require('./lib/rules/boolean-prop-naming'), | ||
'button-has-type': require('./lib/rules/button-has-type'), | ||
'default-props-match-prop-types': require('./lib/rules/default-props-match-prop-types'), | ||
'destructuring-assignment': require('./lib/rules/destructuring-assignment'), | ||
'display-name': require('./lib/rules/display-name'), | ||
'forbid-component-props': require('./lib/rules/forbid-component-props'), | ||
'forbid-dom-props': require('./lib/rules/forbid-dom-props'), | ||
'forbid-elements': require('./lib/rules/forbid-elements'), | ||
'forbid-foreign-prop-types': require('./lib/rules/forbid-foreign-prop-types'), | ||
'forbid-prop-types': require('./lib/rules/forbid-prop-types'), | ||
'function-component-definition': require('./lib/rules/function-component-definition'), | ||
'hook-use-state': require('./lib/rules/hook-use-state'), | ||
'iframe-missing-sandbox': require('./lib/rules/iframe-missing-sandbox'), | ||
'jsx-boolean-value': require('./lib/rules/jsx-boolean-value'), | ||
'jsx-child-element-spacing': require('./lib/rules/jsx-child-element-spacing'), | ||
'jsx-closing-bracket-location': require('./lib/rules/jsx-closing-bracket-location'), | ||
'jsx-closing-tag-location': require('./lib/rules/jsx-closing-tag-location'), | ||
'jsx-curly-spacing': require('./lib/rules/jsx-curly-spacing'), | ||
'jsx-curly-newline': require('./lib/rules/jsx-curly-newline'), | ||
'jsx-equals-spacing': require('./lib/rules/jsx-equals-spacing'), | ||
'jsx-filename-extension': require('./lib/rules/jsx-filename-extension'), | ||
'jsx-first-prop-new-line': require('./lib/rules/jsx-first-prop-new-line'), | ||
'jsx-handler-names': require('./lib/rules/jsx-handler-names'), | ||
'jsx-indent': require('./lib/rules/jsx-indent'), | ||
'jsx-indent-props': require('./lib/rules/jsx-indent-props'), | ||
'jsx-key': require('./lib/rules/jsx-key'), | ||
'jsx-max-depth': require('./lib/rules/jsx-max-depth'), | ||
'jsx-max-props-per-line': require('./lib/rules/jsx-max-props-per-line'), | ||
'jsx-newline': require('./lib/rules/jsx-newline'), | ||
'jsx-no-bind': require('./lib/rules/jsx-no-bind'), | ||
'jsx-no-comment-textnodes': require('./lib/rules/jsx-no-comment-textnodes'), | ||
'jsx-no-constructed-context-values': require('./lib/rules/jsx-no-constructed-context-values'), | ||
'jsx-no-duplicate-props': require('./lib/rules/jsx-no-duplicate-props'), | ||
'jsx-no-leaked-render': require('./lib/rules/jsx-no-leaked-render'), | ||
'jsx-no-literals': require('./lib/rules/jsx-no-literals'), | ||
'jsx-no-script-url': require('./lib/rules/jsx-no-script-url'), | ||
'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank'), | ||
'jsx-no-useless-fragment': require('./lib/rules/jsx-no-useless-fragment'), | ||
'jsx-one-expression-per-line': require('./lib/rules/jsx-one-expression-per-line'), | ||
'jsx-no-undef': require('./lib/rules/jsx-no-undef'), | ||
'jsx-curly-brace-presence': require('./lib/rules/jsx-curly-brace-presence'), | ||
'jsx-pascal-case': require('./lib/rules/jsx-pascal-case'), | ||
'jsx-fragments': require('./lib/rules/jsx-fragments'), | ||
'jsx-props-no-multi-spaces': require('./lib/rules/jsx-props-no-multi-spaces'), | ||
'jsx-props-no-spreading': require('./lib/rules/jsx-props-no-spreading'), | ||
'jsx-sort-default-props': require('./lib/rules/jsx-sort-default-props'), | ||
'jsx-sort-props': require('./lib/rules/jsx-sort-props'), | ||
'jsx-space-before-closing': require('./lib/rules/jsx-space-before-closing'), | ||
'jsx-tag-spacing': require('./lib/rules/jsx-tag-spacing'), | ||
'jsx-uses-react': require('./lib/rules/jsx-uses-react'), | ||
'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'), | ||
'jsx-wrap-multilines': require('./lib/rules/jsx-wrap-multilines'), | ||
'no-invalid-html-attribute': require('./lib/rules/no-invalid-html-attribute'), | ||
'no-access-state-in-setstate': require('./lib/rules/no-access-state-in-setstate'), | ||
'no-adjacent-inline-elements': require('./lib/rules/no-adjacent-inline-elements'), | ||
'no-array-index-key': require('./lib/rules/no-array-index-key'), | ||
'no-arrow-function-lifecycle': require('./lib/rules/no-arrow-function-lifecycle'), | ||
'no-children-prop': require('./lib/rules/no-children-prop'), | ||
'no-danger': require('./lib/rules/no-danger'), | ||
'no-danger-with-children': require('./lib/rules/no-danger-with-children'), | ||
'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-direct-mutation-state': require('./lib/rules/no-direct-mutation-state'), | ||
'no-find-dom-node': require('./lib/rules/no-find-dom-node'), | ||
'no-is-mounted': require('./lib/rules/no-is-mounted'), | ||
'no-multi-comp': require('./lib/rules/no-multi-comp'), | ||
'no-namespace': require('./lib/rules/no-namespace'), | ||
'no-set-state': require('./lib/rules/no-set-state'), | ||
'no-string-refs': require('./lib/rules/no-string-refs'), | ||
'no-redundant-should-component-update': require('./lib/rules/no-redundant-should-component-update'), | ||
'no-render-return-value': require('./lib/rules/no-render-return-value'), | ||
'no-this-in-sfc': require('./lib/rules/no-this-in-sfc'), | ||
'no-typos': require('./lib/rules/no-typos'), | ||
'no-unescaped-entities': require('./lib/rules/no-unescaped-entities'), | ||
'no-unknown-property': require('./lib/rules/no-unknown-property'), | ||
'no-unsafe': require('./lib/rules/no-unsafe'), | ||
'no-unstable-nested-components': require('./lib/rules/no-unstable-nested-components'), | ||
'no-unused-class-component-methods': require('./lib/rules/no-unused-class-component-methods'), | ||
'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'), | ||
'no-unused-state': require('./lib/rules/no-unused-state'), | ||
'no-will-update-set-state': require('./lib/rules/no-will-update-set-state'), | ||
'prefer-es6-class': require('./lib/rules/prefer-es6-class'), | ||
'prefer-exact-props': require('./lib/rules/prefer-exact-props'), | ||
'prefer-read-only-props': require('./lib/rules/prefer-read-only-props'), | ||
'prefer-stateless-function': require('./lib/rules/prefer-stateless-function'), | ||
'prop-types': require('./lib/rules/prop-types'), | ||
'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'), | ||
'require-default-props': require('./lib/rules/require-default-props'), | ||
'require-optimization': require('./lib/rules/require-optimization'), | ||
'require-render-return': require('./lib/rules/require-render-return'), | ||
'self-closing-comp': require('./lib/rules/self-closing-comp'), | ||
'sort-comp': require('./lib/rules/sort-comp'), | ||
'sort-prop-types': require('./lib/rules/sort-prop-types'), | ||
'state-in-constructor': require('./lib/rules/state-in-constructor'), | ||
'static-property-placement': require('./lib/rules/static-property-placement'), | ||
'style-prop-object': require('./lib/rules/style-prop-object'), | ||
'void-dom-elements-no-children': require('./lib/rules/void-dom-elements-no-children'), | ||
}; | ||
/* eslint-enable */ | ||
const allRules = require('./lib/rules'); | ||
function filterRules(rules, predicate) { | ||
return fromEntries(entries(rules).filter((entry) => predicate(entry[1]))); | ||
} | ||
// for legacy config system | ||
const plugins = [ | ||
'react', | ||
]; | ||
function configureAsError(rules) { | ||
return fromEntries(Object.keys(rules).map((key) => [`react/${key}`, 2])); | ||
} | ||
const activeRules = filterRules(allRules, (rule) => !rule.meta.deprecated); | ||
const activeRulesConfig = configureAsError(activeRules); | ||
const deprecatedRules = filterRules(allRules, (rule) => rule.meta.deprecated); | ||
module.exports = { | ||
deprecatedRules, | ||
deprecatedRules: configAll.plugins.react.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/no-unsafe': 0, | ||
'react/prop-types': 2, | ||
'react/react-in-jsx-scope': 2, | ||
'react/require-render-return': 2, | ||
}, | ||
}, | ||
all: { | ||
plugins: [ | ||
'react', | ||
], | ||
parserOptions: { | ||
ecmaFeatures: { | ||
jsx: true, | ||
}, | ||
}, | ||
rules: activeRulesConfig, | ||
}, | ||
'jsx-runtime': { | ||
plugins: [ | ||
'react', | ||
], | ||
parserOptions: { | ||
ecmaFeatures: { | ||
jsx: true, | ||
}, | ||
jsxPragma: null, // for @typescript/eslint-parser | ||
}, | ||
rules: { | ||
'react/react-in-jsx-scope': 0, | ||
'react/jsx-uses-react': 0, | ||
}, | ||
}, | ||
recommended: Object.assign({}, configRecommended, { | ||
parserOptions: configRecommended.languageOptions.parserOptions, | ||
plugins, | ||
}), | ||
all: Object.assign({}, configAll, { | ||
parserOptions: configAll.languageOptions.parserOptions, | ||
plugins, | ||
}), | ||
'jsx-runtime': Object.assign({}, configRuntime, { | ||
parserOptions: configRuntime.languageOptions.parserOptions, | ||
plugins, | ||
}), | ||
}, | ||
}; |
@@ -8,2 +8,4 @@ /** | ||
const values = require('object.values'); | ||
const Components = require('../util/Components'); | ||
@@ -366,7 +368,5 @@ const propsUtil = require('../util/props'); | ||
const list = components.list(); | ||
values(components.list()).forEach((component) => { | ||
const annotation = getComponentTypeAnnotation(component); | ||
Object.keys(list).forEach((component) => { | ||
const annotation = getComponentTypeAnnotation(list[component]); | ||
if (annotation) { | ||
@@ -385,3 +385,3 @@ let propType; | ||
validatePropNaming( | ||
list[component].node, | ||
component.node, | ||
prop.properties || prop.members | ||
@@ -393,4 +393,4 @@ ); | ||
if (list[component].invalidProps && list[component].invalidProps.length > 0) { | ||
reportInvalidNaming(list[component]); | ||
if (component.invalidProps && component.invalidProps.length > 0) { | ||
reportInvalidNaming(component); | ||
} | ||
@@ -397,0 +397,0 @@ }); |
@@ -9,2 +9,4 @@ /** | ||
const values = require('object.values'); | ||
const Components = require('../util/Components'); | ||
@@ -95,11 +97,11 @@ const docsUrl = require('../util/docsUrl'); | ||
'Program:exit'() { | ||
const list = components.list(); | ||
// If no defaultProps could be found, we don't report anything. | ||
Object.keys(list).filter((component) => list[component].defaultProps).forEach((component) => { | ||
reportInvalidDefaultProps( | ||
list[component].declaredPropTypes, | ||
list[component].defaultProps || {} | ||
); | ||
}); | ||
values(components.list()) | ||
.filter((component) => component.defaultProps) | ||
.forEach((component) => { | ||
reportInvalidDefaultProps( | ||
component.declaredPropTypes, | ||
component.defaultProps || {} | ||
); | ||
}); | ||
}, | ||
@@ -106,0 +108,0 @@ }; |
@@ -11,2 +11,3 @@ /** | ||
const report = require('../util/report'); | ||
const testReactVersion = require('../util/version').testReactVersion; | ||
@@ -98,2 +99,4 @@ const DEFAULT_OPTION = 'always'; | ||
// set to save renamed var of useContext | ||
const contextSet = new Set(); | ||
/** | ||
@@ -133,3 +136,3 @@ * @param {ASTNode} node We expect either an ArrowFunctionExpression, | ||
const contextName = sfcParams.contextName(); | ||
// props.aProp || context.aProp | ||
// props.aProp | ||
const isPropUsed = ( | ||
@@ -140,3 +143,3 @@ (propsName && node.object.name === propsName) | ||
&& !isAssignmentLHS(node); | ||
if (isPropUsed && configuration === 'always') { | ||
if (isPropUsed && configuration === 'always' && !node.optional) { | ||
report(context, messages.useDestructAssignment, 'useDestructAssignment', { | ||
@@ -149,2 +152,17 @@ node, | ||
} | ||
// const foo = useContext(aContext); | ||
// foo.aProp | ||
const isContextUsed = contextSet.has(node.object.name) && !isAssignmentLHS(node); | ||
const optional = node.optional | ||
// the below is for the old typescript-eslint parser | ||
|| context.getSourceCode().getText(node).slice(node.object.range[1] - node.range[0], node.object.range[1] - node.range[0] + 1) === '?'; | ||
if (isContextUsed && configuration === 'always' && !optional) { | ||
report(context, messages.useDestructAssignment, 'useDestructAssignment', { | ||
node, | ||
data: { | ||
type: node.object.name, | ||
}, | ||
}); | ||
} | ||
} | ||
@@ -184,4 +202,5 @@ | ||
const hasHooks = testReactVersion(context, '>= 16.9'); | ||
return { | ||
FunctionDeclaration: handleStatelessComponent, | ||
@@ -221,4 +240,9 @@ | ||
const destructuring = (node.init && node.id && node.id.type === 'ObjectPattern'); | ||
const identifier = (node.init && node.id && node.id.type === 'Identifier'); | ||
// let {foo} = props; | ||
const destructuringSFC = destructuring && (node.init.name === 'props' || node.init.name === 'context'); | ||
const destructuringSFC = destructuring && node.init.name === 'props'; | ||
// let {foo} = useContext(aContext); | ||
const destructuringUseContext = hasHooks && destructuring && node.init.callee && node.init.callee.name === 'useContext'; | ||
// let foo = useContext(aContext); | ||
const assignUseContext = hasHooks && identifier && node.init.callee && node.init.callee.name === 'useContext'; | ||
// let {foo} = this.props; | ||
@@ -229,2 +253,15 @@ const destructuringClass = destructuring && node.init.object && node.init.object.type === 'ThisExpression' && ( | ||
if (SFCComponent && assignUseContext) { | ||
contextSet.add(node.id.name); | ||
} | ||
if (SFCComponent && destructuringUseContext && configuration === 'never') { | ||
report(context, messages.noDestructAssignment, 'noDestructAssignment', { | ||
node, | ||
data: { | ||
type: node.init.callee.name, | ||
}, | ||
}); | ||
} | ||
if (SFCComponent && destructuringSFC && configuration === 'never') { | ||
@@ -231,0 +268,0 @@ report(context, messages.noDestructAssignment, 'noDestructAssignment', { |
@@ -42,3 +42,3 @@ /** | ||
items: { | ||
oneOf: [{ | ||
anyOf: [{ | ||
type: 'string', | ||
@@ -45,0 +45,0 @@ }, { |
@@ -57,3 +57,3 @@ /** | ||
items: { | ||
oneOf: [{ | ||
anyOf: [{ | ||
type: 'string', | ||
@@ -60,0 +60,0 @@ }, { |
@@ -135,3 +135,3 @@ /** | ||
namedComponents: { | ||
oneOf: [ | ||
anyOf: [ | ||
{ | ||
@@ -158,3 +158,3 @@ enum: [ | ||
unnamedComponents: { | ||
oneOf: [ | ||
anyOf: [ | ||
{ enum: ['arrow-function', 'function-expression'] }, | ||
@@ -161,0 +161,0 @@ { |
@@ -11,2 +11,3 @@ /** | ||
const report = require('../util/report'); | ||
const getMessageData = require('../util/message'); | ||
@@ -17,4 +18,11 @@ // ------------------------------------------------------------------------------ | ||
function isNodeDestructuring(node) { | ||
return node && (node.type === 'ArrayPattern' || node.type === 'ObjectPattern'); | ||
} | ||
const messages = { | ||
useStateErrorMessage: 'useState call is not destructured into value + setter pair', | ||
useStateErrorMessageOrAddOption: 'useState call is not destructured into value + setter pair (you can allow destructuring by enabling "allowDestructuredState" option)', | ||
suggestPair: 'Destructure useState call into value + setter pair', | ||
suggestMemo: 'Replace useState call with useMemo', | ||
}; | ||
@@ -31,3 +39,12 @@ | ||
messages, | ||
schema: [], | ||
schema: [{ | ||
type: 'object', | ||
properties: { | ||
allowDestructuredState: { | ||
default: false, | ||
type: 'boolean', | ||
}, | ||
}, | ||
additionalProperties: false, | ||
}], | ||
type: 'suggestion', | ||
@@ -37,128 +54,154 @@ hasSuggestions: true, | ||
create: Components.detect((context, components, util) => ({ | ||
CallExpression(node) { | ||
const isImmediateReturn = node.parent | ||
&& node.parent.type === 'ReturnStatement'; | ||
create: Components.detect((context, components, util) => { | ||
const configuration = context.options[0] || {}; | ||
const allowDestructuredState = configuration.allowDestructuredState || false; | ||
if (isImmediateReturn || !util.isReactHookCall(node, ['useState'])) { | ||
return; | ||
} | ||
return { | ||
CallExpression(node) { | ||
const isImmediateReturn = node.parent | ||
&& node.parent.type === 'ReturnStatement'; | ||
const isDestructuringDeclarator = node.parent | ||
&& node.parent.type === 'VariableDeclarator' | ||
&& node.parent.id.type === 'ArrayPattern'; | ||
if (isImmediateReturn || !util.isReactHookCall(node, ['useState'])) { | ||
return; | ||
} | ||
if (!isDestructuringDeclarator) { | ||
report( | ||
context, | ||
messages.useStateErrorMessage, | ||
'useStateErrorMessage', | ||
{ node } | ||
); | ||
return; | ||
} | ||
const isDestructuringDeclarator = node.parent | ||
&& node.parent.type === 'VariableDeclarator' | ||
&& node.parent.id.type === 'ArrayPattern'; | ||
const variableNodes = node.parent.id.elements; | ||
const valueVariable = variableNodes[0]; | ||
const setterVariable = variableNodes[1]; | ||
if (!isDestructuringDeclarator) { | ||
report( | ||
context, | ||
messages.useStateErrorMessage, | ||
'useStateErrorMessage', | ||
{ | ||
node, | ||
suggest: false, | ||
} | ||
); | ||
return; | ||
} | ||
const valueVariableName = valueVariable | ||
? valueVariable.name | ||
: undefined; | ||
const variableNodes = node.parent.id.elements; | ||
const valueVariable = variableNodes[0]; | ||
const setterVariable = variableNodes[1]; | ||
const isOnlyValueDestructuring = isNodeDestructuring(valueVariable) && !isNodeDestructuring(setterVariable); | ||
const setterVariableName = setterVariable | ||
? setterVariable.name | ||
: undefined; | ||
if (allowDestructuredState && isOnlyValueDestructuring) { | ||
return; | ||
} | ||
const caseCandidateMatch = valueVariableName ? valueVariableName.match(/(^[a-z]+)(.*)/) : undefined; | ||
const upperCaseCandidatePrefix = caseCandidateMatch ? caseCandidateMatch[1] : undefined; | ||
const caseCandidateSuffix = caseCandidateMatch ? caseCandidateMatch[2] : undefined; | ||
const expectedSetterVariableNames = upperCaseCandidatePrefix ? [ | ||
`set${upperCaseCandidatePrefix.charAt(0).toUpperCase()}${upperCaseCandidatePrefix.slice(1)}${caseCandidateSuffix}`, | ||
`set${upperCaseCandidatePrefix.toUpperCase()}${caseCandidateSuffix}`, | ||
] : []; | ||
const valueVariableName = valueVariable | ||
? valueVariable.name | ||
: undefined; | ||
const isSymmetricGetterSetterPair = valueVariable | ||
&& setterVariable | ||
&& expectedSetterVariableNames.indexOf(setterVariableName) !== -1 | ||
&& variableNodes.length === 2; | ||
const setterVariableName = setterVariable | ||
? setterVariable.name | ||
: undefined; | ||
if (!isSymmetricGetterSetterPair) { | ||
const suggestions = [ | ||
{ | ||
desc: 'Destructure useState call into value + setter pair', | ||
fix: (fixer) => { | ||
if (expectedSetterVariableNames.length === 0) { | ||
return; | ||
const caseCandidateMatch = valueVariableName ? valueVariableName.match(/(^[a-z]+)(.*)/) : undefined; | ||
const upperCaseCandidatePrefix = caseCandidateMatch ? caseCandidateMatch[1] : undefined; | ||
const caseCandidateSuffix = caseCandidateMatch ? caseCandidateMatch[2] : undefined; | ||
const expectedSetterVariableNames = upperCaseCandidatePrefix ? [ | ||
`set${upperCaseCandidatePrefix.charAt(0).toUpperCase()}${upperCaseCandidatePrefix.slice(1)}${caseCandidateSuffix}`, | ||
`set${upperCaseCandidatePrefix.toUpperCase()}${caseCandidateSuffix}`, | ||
] : []; | ||
const isSymmetricGetterSetterPair = valueVariable | ||
&& setterVariable | ||
&& expectedSetterVariableNames.indexOf(setterVariableName) !== -1 | ||
&& variableNodes.length === 2; | ||
if (!isSymmetricGetterSetterPair) { | ||
const suggestions = [ | ||
Object.assign( | ||
getMessageData('suggestPair', messages.suggestPair), | ||
{ | ||
fix(fixer) { | ||
if (expectedSetterVariableNames.length > 0) { | ||
return fixer.replaceTextRange( | ||
node.parent.id.range, | ||
`[${valueVariableName}, ${expectedSetterVariableNames[0]}]` | ||
); | ||
} | ||
}, | ||
} | ||
), | ||
]; | ||
const fix = fixer.replaceTextRange( | ||
node.parent.id.range, | ||
`[${valueVariableName}, ${expectedSetterVariableNames[0]}]` | ||
); | ||
const defaultReactImports = components.getDefaultReactImports(); | ||
const defaultReactImportSpecifier = defaultReactImports | ||
? defaultReactImports[0] | ||
: undefined; | ||
return fix; | ||
}, | ||
}, | ||
]; | ||
const defaultReactImportName = defaultReactImportSpecifier | ||
? defaultReactImportSpecifier.local.name | ||
: undefined; | ||
const defaultReactImports = components.getDefaultReactImports(); | ||
const defaultReactImportSpecifier = defaultReactImports | ||
? defaultReactImports[0] | ||
: undefined; | ||
const namedReactImports = components.getNamedReactImports(); | ||
const useStateReactImportSpecifier = namedReactImports | ||
? namedReactImports.find((specifier) => specifier.imported.name === 'useState') | ||
: undefined; | ||
const defaultReactImportName = defaultReactImportSpecifier | ||
? defaultReactImportSpecifier.local.name | ||
: undefined; | ||
const isSingleGetter = valueVariable && variableNodes.length === 1; | ||
const isUseStateCalledWithSingleArgument = node.arguments.length === 1; | ||
if (isSingleGetter && isUseStateCalledWithSingleArgument) { | ||
const useMemoReactImportSpecifier = namedReactImports | ||
&& namedReactImports.find((specifier) => specifier.imported.name === 'useMemo'); | ||
const namedReactImports = components.getNamedReactImports(); | ||
const useStateReactImportSpecifier = namedReactImports | ||
? namedReactImports.find((specifier) => specifier.imported.name === 'useState') | ||
: undefined; | ||
let useMemoCode; | ||
if (useMemoReactImportSpecifier) { | ||
useMemoCode = useMemoReactImportSpecifier.local.name; | ||
} else if (defaultReactImportName) { | ||
useMemoCode = `${defaultReactImportName}.useMemo`; | ||
} else { | ||
useMemoCode = 'useMemo'; | ||
} | ||
const isSingleGetter = valueVariable && variableNodes.length === 1; | ||
const isUseStateCalledWithSingleArgument = node.arguments.length === 1; | ||
if (isSingleGetter && isUseStateCalledWithSingleArgument) { | ||
const useMemoReactImportSpecifier = namedReactImports | ||
&& namedReactImports.find((specifier) => specifier.imported.name === 'useMemo'); | ||
suggestions.unshift(Object.assign( | ||
getMessageData('suggestMemo', messages.suggestMemo), | ||
{ | ||
fix: (fixer) => [ | ||
// Add useMemo import, if necessary | ||
useStateReactImportSpecifier | ||
&& (!useMemoReactImportSpecifier || defaultReactImportName) | ||
&& fixer.insertTextAfter(useStateReactImportSpecifier, ', useMemo'), | ||
// Convert single-value destructure to simple assignment | ||
fixer.replaceTextRange(node.parent.id.range, valueVariableName), | ||
// Convert useState call to useMemo + arrow function + dependency array | ||
fixer.replaceTextRange( | ||
node.range, | ||
`${useMemoCode}(() => ${context.getSourceCode().getText(node.arguments[0])}, [])` | ||
), | ||
].filter(Boolean), | ||
} | ||
)); | ||
} | ||
let useMemoCode; | ||
if (useMemoReactImportSpecifier) { | ||
useMemoCode = useMemoReactImportSpecifier.local.name; | ||
} else if (defaultReactImportName) { | ||
useMemoCode = `${defaultReactImportName}.useMemo`; | ||
} else { | ||
useMemoCode = 'useMemo'; | ||
if (isOnlyValueDestructuring) { | ||
report( | ||
context, | ||
messages.useStateErrorMessageOrAddOption, | ||
'useStateErrorMessageOrAddOption', | ||
{ | ||
node: node.parent.id, | ||
suggest: false, | ||
} | ||
); | ||
return; | ||
} | ||
suggestions.unshift({ | ||
desc: 'Replace useState call with useMemo', | ||
fix: (fixer) => [ | ||
// Add useMemo import, if necessary | ||
useStateReactImportSpecifier | ||
&& (!useMemoReactImportSpecifier || defaultReactImportName) | ||
&& fixer.insertTextAfter(useStateReactImportSpecifier, ', useMemo'), | ||
// Convert single-value destructure to simple assignment | ||
fixer.replaceTextRange(node.parent.id.range, valueVariableName), | ||
// Convert useState call to useMemo + arrow function + dependency array | ||
fixer.replaceTextRange( | ||
node.range, | ||
`${useMemoCode}(() => ${context.getSourceCode().getText(node.arguments[0])}, [])` | ||
), | ||
].filter(Boolean), | ||
}); | ||
report( | ||
context, | ||
messages.useStateErrorMessage, | ||
'useStateErrorMessage', | ||
{ | ||
node: node.parent.id, | ||
suggest: suggestions, | ||
} | ||
); | ||
} | ||
report( | ||
context, | ||
messages.useStateErrorMessage, | ||
'useStateErrorMessage', | ||
{ | ||
node: node.parent.id, | ||
suggest: suggestions, | ||
} | ||
); | ||
} | ||
}, | ||
})), | ||
}, | ||
}; | ||
}), | ||
}; |
@@ -33,3 +33,3 @@ /** | ||
schema: [{ | ||
oneOf: [ | ||
anyOf: [ | ||
{ | ||
@@ -36,0 +36,0 @@ enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned'], |
@@ -53,3 +53,3 @@ /** | ||
{ | ||
oneOf: [ | ||
anyOf: [ | ||
{ | ||
@@ -56,0 +56,0 @@ type: 'object', |
@@ -59,3 +59,3 @@ /** | ||
{ | ||
oneOf: [ | ||
anyOf: [ | ||
{ | ||
@@ -62,0 +62,0 @@ enum: ['consistent', 'never'], |
@@ -71,3 +71,3 @@ /** | ||
basicConfigOrBoolean: { | ||
oneOf: [{ | ||
anyOf: [{ | ||
$ref: '#/definitions/basicConfig', | ||
@@ -81,3 +81,3 @@ }, { | ||
items: [{ | ||
oneOf: [{ | ||
anyOf: [{ | ||
allOf: [{ | ||
@@ -84,0 +84,0 @@ $ref: '#/definitions/basicConfig', |
@@ -58,3 +58,3 @@ /** | ||
schema: [{ | ||
oneOf: [{ | ||
anyOf: [{ | ||
enum: ['tab', 'first'], | ||
@@ -67,3 +67,3 @@ }, { | ||
indentMode: { | ||
oneOf: [{ | ||
anyOf: [{ | ||
enum: ['tab', 'first'], | ||
@@ -70,0 +70,0 @@ }, { |
@@ -61,3 +61,3 @@ /** | ||
schema: [{ | ||
oneOf: [{ | ||
anyOf: [{ | ||
enum: ['tab'], | ||
@@ -64,0 +64,0 @@ }, { |
@@ -152,6 +152,16 @@ /** | ||
const isArrFn = node && node.type === 'ArrowFunctionExpression'; | ||
if (isArrFn && (node.body.type === 'JSXElement' || node.body.type === 'JSXFragment')) { | ||
const shouldCheckNode = (n) => n && (n.type === 'JSXElement' || n.type === 'JSXFragment'); | ||
if (isArrFn && shouldCheckNode(node.body)) { | ||
checkIteratorElement(node.body); | ||
} | ||
if (node.body.type === 'ConditionalExpression') { | ||
if (shouldCheckNode(node.body.consequent)) { | ||
checkIteratorElement(node.body.consequent); | ||
} | ||
if (shouldCheckNode(node.body.alternate)) { | ||
checkIteratorElement(node.body.alternate); | ||
} | ||
} else if (node.body.type === 'LogicalExpression' && shouldCheckNode(node.body.right)) { | ||
checkIteratorElement(node.body.right); | ||
} | ||
} | ||
@@ -158,0 +168,0 @@ |
@@ -123,7 +123,3 @@ /** | ||
baseDepth += 1; | ||
(children || []).forEach((node) => { | ||
if (!hasJSX(node)) { | ||
return; | ||
} | ||
(children || []).filter((node) => hasJSX(node)).forEach((node) => { | ||
if (baseDepth > maxDepth) { | ||
@@ -130,0 +126,0 @@ report(node, baseDepth); |
@@ -75,2 +75,11 @@ /** | ||
function isBlockCommentInCurlyBraces(element) { | ||
const elementRawValue = sourceCode.getText(element); | ||
return /^\s*{\/\*/.test(elementRawValue); | ||
} | ||
function isNonBlockComment(element) { | ||
return !isBlockCommentInCurlyBraces(element) && (element.type === 'JSXElement' || element.type === 'JSXExpressionContainer'); | ||
} | ||
return { | ||
@@ -97,3 +106,11 @@ 'Program:exit'() { | ||
if (allowMultilines && (isMultilined(element) || isMultilined(secondAdjacentSibling))) { | ||
if (isBlockCommentInCurlyBraces(element)) return; | ||
if ( | ||
allowMultilines | ||
&& ( | ||
isMultilined(element) | ||
|| isMultilined(elements.slice(index + 2).find(isNonBlockComment)) | ||
) | ||
) { | ||
if (!isWithoutNewLine) return; | ||
@@ -120,2 +137,3 @@ | ||
if (isWithoutNewLine === prevent) return; | ||
const messageId = prevent | ||
@@ -122,0 +140,0 @@ ? 'prevent' |
@@ -141,2 +141,3 @@ /** | ||
messages, | ||
schema: {}, | ||
}, | ||
@@ -143,0 +144,0 @@ |
@@ -10,2 +10,3 @@ /** | ||
const report = require('../util/report'); | ||
const testReactVersion = require('../util/version').testReactVersion; | ||
const isParenthesized = require('../util/ast').isParenthesized; | ||
@@ -64,5 +65,23 @@ | ||
} | ||
if (node.parent && node.parent.type === 'ConditionalExpression' && node.parent.consequent.value === false) { | ||
return `${getIsCoerceValidNestedLogicalExpression(node) ? '' : '!'}${nodeText}`; | ||
} | ||
return `${getIsCoerceValidNestedLogicalExpression(node) ? '' : '!!'}${nodeText}`; | ||
}).join(' && '); | ||
if (rightNode.parent && rightNode.parent.type === 'ConditionalExpression' && rightNode.parent.consequent.value === false) { | ||
const consequentVal = rightNode.parent.consequent.raw || rightNode.parent.consequent.name; | ||
const alternateVal = rightNode.parent.alternate.raw || rightNode.parent.alternate.name; | ||
if (rightNode.parent.test && rightNode.parent.test.type === 'LogicalExpression') { | ||
return fixer.replaceText(reportedNode, `${newText} ? ${consequentVal} : ${alternateVal}`); | ||
} | ||
return fixer.replaceText(reportedNode, `${newText} && ${alternateVal}`); | ||
} | ||
if (rightNode.type === 'ConditionalExpression') { | ||
return fixer.replaceText(reportedNode, `${newText} && (${rightSideText})`); | ||
} | ||
if (rightNode.type === 'Literal') { | ||
return null; | ||
} | ||
return fixer.replaceText(reportedNode, `${newText} && ${rightSideText}`); | ||
@@ -135,2 +154,5 @@ } | ||
if (testReactVersion(context, '>= 18') && leftSide.type === 'Literal' && leftSide.value === '') { | ||
return; | ||
} | ||
report(context, messages.noPotentialLeakedRender, 'noPotentialLeakedRender', { | ||
@@ -137,0 +159,0 @@ node, |
@@ -68,3 +68,9 @@ /** | ||
function getStringFromValue(value) { | ||
/** | ||
* Get the string(s) from a value | ||
* @param {ASTNode} value The AST node being checked. | ||
* @param {ASTNode} targetValue The AST node being checked. | ||
* @returns {String | String[] | null} The string value, or null if not a string. | ||
*/ | ||
function getStringFromValue(value, targetValue) { | ||
if (value) { | ||
@@ -79,7 +85,15 @@ if (value.type === 'Literal') { | ||
const expr = value.expression; | ||
return expr && ( | ||
expr.type === 'ConditionalExpression' | ||
? [expr.consequent.value, expr.alternate.value] | ||
: expr.value | ||
); | ||
if (expr && expr.type === 'ConditionalExpression') { | ||
const relValues = [expr.consequent.value, expr.alternate.value]; | ||
if (targetValue.type === 'JSXExpressionContainer' && targetValue.expression && targetValue.expression.type === 'ConditionalExpression') { | ||
const targetTestCond = targetValue.expression.test.name; | ||
const relTestCond = value.expression.test.name; | ||
if (targetTestCond === relTestCond) { | ||
const targetBlankIndex = [targetValue.expression.consequent.value, targetValue.expression.alternate.value].indexOf('_blank'); | ||
return relValues[targetBlankIndex]; | ||
} | ||
} | ||
return relValues; | ||
} | ||
return expr.value; | ||
} | ||
@@ -92,2 +106,3 @@ } | ||
const relIndex = findLastIndex(node.attributes, (attr) => (attr.type === 'JSXAttribute' && attr.name.name === 'rel')); | ||
const targetIndex = findLastIndex(node.attributes, (attr) => (attr.type === 'JSXAttribute' && attr.name.name === 'target')); | ||
if (relIndex === -1 || (warnOnSpreadAttributes && relIndex < spreadAttributeIndex)) { | ||
@@ -98,3 +113,4 @@ return false; | ||
const relAttribute = node.attributes[relIndex]; | ||
const value = getStringFromValue(relAttribute.value); | ||
const targetAttributeValue = node.attributes[targetIndex] && node.attributes[targetIndex].value; | ||
const value = getStringFromValue(relAttribute.value, targetAttributeValue); | ||
return [].concat(value).every((item) => { | ||
@@ -101,0 +117,0 @@ const tags = typeof item === 'string' ? item.toLowerCase().split(' ') : false; |
@@ -97,2 +97,10 @@ /** | ||
messages, | ||
schema: [{ | ||
type: 'object', | ||
properties: { | ||
allowExpressions: { | ||
type: 'boolean', | ||
}, | ||
}, | ||
}], | ||
}, | ||
@@ -99,0 +107,0 @@ |
/** | ||
* @fileoverview Enforce default props alphabetical sorting | ||
* @author Vladimir Kattsov | ||
* @deprecated | ||
*/ | ||
@@ -11,3 +12,6 @@ | ||
const report = require('../util/report'); | ||
const log = require('../util/log'); | ||
let isWarnedForDeprecation = false; | ||
// ------------------------------------------------------------------------------ | ||
@@ -23,2 +27,4 @@ // Rule Definition | ||
meta: { | ||
deprecated: true, | ||
replacedBy: ['sort-default-props'], | ||
docs: { | ||
@@ -173,4 +179,13 @@ description: 'Enforce defaultProps declarations alphabetical sorting', | ||
}, | ||
Program() { | ||
if (isWarnedForDeprecation) { | ||
return; | ||
} | ||
log('The react/jsx-sort-default-props rule is deprecated. It has been renamed to `react/sort-default-props`.'); | ||
isWarnedForDeprecation = true; | ||
}, | ||
}; | ||
}, | ||
}; |
@@ -10,2 +10,4 @@ /** | ||
const includes = require('array-includes'); | ||
const toSorted = require('array.prototype.tosorted'); | ||
const docsUrl = require('../util/docsUrl'); | ||
@@ -243,3 +245,3 @@ const jsxUtil = require('../util/jsx'); | ||
.slice(0) | ||
.map((group) => group.slice(0).sort((a, b) => contextCompare(a, b, options))); | ||
.map((group) => toSorted(group, (a, b) => contextCompare(a, b, options))); | ||
@@ -246,0 +248,0 @@ return function fixFunction(fixer) { |
@@ -28,2 +28,3 @@ /** | ||
deprecated: true, | ||
replacedBy: ['jsx-tag-spacing'], | ||
docs: { | ||
@@ -30,0 +31,0 @@ description: 'Enforce spacing before closing bracket in JSX', |
@@ -192,6 +192,3 @@ /** | ||
} | ||
node.specifiers.forEach((specifier) => { | ||
if (!specifier.imported) { | ||
return; | ||
} | ||
node.specifiers.filter(((s) => s.imported)).forEach((specifier) => { | ||
checkDeprecation(node, `${MODULES[node.source.value][0]}.${specifier.imported.name}`); | ||
@@ -216,6 +213,4 @@ }); | ||
} | ||
node.id.properties.forEach((property) => { | ||
if (property.type !== 'RestElement' && property.key) { | ||
checkDeprecation(node, `${reactModuleName || pragma}.${property.key.name}`); | ||
} | ||
node.id.properties.filter((p) => p.type !== 'RestElement' && p.key).forEach((property) => { | ||
checkDeprecation(node, `${reactModuleName || pragma}.${property.key.name}`); | ||
}); | ||
@@ -222,0 +217,0 @@ }, |
@@ -9,2 +9,4 @@ /** | ||
const values = require('object.values'); | ||
const Components = require('../util/Components'); | ||
@@ -145,9 +147,7 @@ const componentUtil = require('../util/componentUtil'); | ||
'Program:exit'() { | ||
const list = components.list(); | ||
Object.keys(list).forEach((key) => { | ||
if (!isValid(list[key])) { | ||
reportMutations(list[key]); | ||
} | ||
}); | ||
values(components.list()) | ||
.filter((component) => !isValid(component)) | ||
.forEach((component) => { | ||
reportMutations(component); | ||
}); | ||
}, | ||
@@ -154,0 +154,0 @@ }; |
@@ -11,2 +11,3 @@ /** | ||
const report = require('../util/report'); | ||
const getMessageData = require('../util/message'); | ||
@@ -236,2 +237,7 @@ // ------------------------------------------------------------------------------ | ||
spaceDelimited: 'β{{attributeName}}β attribute values should be space delimited.', | ||
suggestRemoveDefault: '"remove {{attributeName}}"', | ||
suggestRemoveEmpty: '"remove empty attribute {{attributeName}}"', | ||
suggestRemoveInvalid: 'βremove invalid attribute {{reportingValue}}β', | ||
suggestRemoveWhitespaces: 'remove whitespaces in β{{reportingValue}}β', | ||
suggestRemoveNonString: 'remove non-string value in β{{reportingValue}}β', | ||
}; | ||
@@ -259,5 +265,8 @@ | ||
data: { attributeName }, | ||
fix(fixer) { | ||
return fixer.remove(parentNode); | ||
}, | ||
suggest: [ | ||
Object.assign( | ||
getMessageData('suggestRemoveNonString', messages.suggestRemoveNonString), | ||
{ fix(fixer) { return fixer.remove(parentNode); } } | ||
), | ||
], | ||
}); | ||
@@ -271,5 +280,8 @@ return; | ||
data: { attributeName }, | ||
fix(fixer) { | ||
return fixer.remove(parentNode); | ||
}, | ||
suggest: [ | ||
Object.assign( | ||
getMessageData('suggestRemoveEmpty', messages.suggestRemoveEmpty), | ||
{ fix(fixer) { return fixer.remove(node.parent); } } | ||
), | ||
], | ||
}); | ||
@@ -283,12 +295,19 @@ return; | ||
const reportingValue = singlePart.reportingValue; | ||
const suggest = [ | ||
Object.assign( | ||
getMessageData('suggestRemoveInvalid', messages.suggestRemoveInvalid), | ||
{ fix(fixer) { return fixer.removeRange(singlePart.range); } } | ||
), | ||
]; | ||
if (!allowedTags) { | ||
const data = { | ||
attributeName, | ||
reportingValue, | ||
}; | ||
report(context, messages.neverValid, 'neverValid', { | ||
node, | ||
data: { | ||
attributeName, | ||
reportingValue, | ||
}, | ||
fix(fixer) { | ||
return fixer.removeRange(singlePart.range); | ||
}, | ||
data, | ||
suggest, | ||
}); | ||
@@ -303,5 +322,3 @@ } else if (!allowedTags.has(parentNodeName)) { | ||
}, | ||
fix(fixer) { | ||
return fixer.removeRange(singlePart.range); | ||
}, | ||
suggest, | ||
}); | ||
@@ -333,2 +350,3 @@ } | ||
}, | ||
suggest: false, | ||
}); | ||
@@ -347,5 +365,8 @@ } | ||
data: { attributeName }, | ||
fix(fixer) { | ||
return fixer.removeRange(whitespacePart.range); | ||
}, | ||
suggest: [ | ||
Object.assign( | ||
getMessageData('suggestRemoveWhitespaces', messages.suggestRemoveWhitespaces), | ||
{ fix(fixer) { return fixer.removeRange(whitespacePart.range); } } | ||
), | ||
], | ||
}); | ||
@@ -356,5 +377,8 @@ } else if (whitespacePart.value !== '\u0020') { | ||
data: { attributeName }, | ||
fix(fixer) { | ||
return fixer.replaceTextRange(whitespacePart.range, '\u0020'); | ||
}, | ||
suggest: [ | ||
Object.assign( | ||
getMessageData('suggestRemoveWhitespaces', messages.suggestRemoveWhitespaces), | ||
{ fix(fixer) { return fixer.replaceTextRange(whitespacePart.range, '\u0020'); } } | ||
), | ||
], | ||
}); | ||
@@ -370,6 +394,2 @@ } | ||
function fix(fixer) { | ||
return fixer.remove(node); | ||
} | ||
const parentNodeName = node.parent.name.name; | ||
@@ -387,3 +407,8 @@ if (!COMPONENT_ATTRIBUTE_MAP.has(attribute) || !COMPONENT_ATTRIBUTE_MAP.get(attribute).has(parentNodeName)) { | ||
}, | ||
fix, | ||
suggest: [ | ||
Object.assign( | ||
getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault), | ||
{ fix(fixer) { return fixer.remove(node); } } | ||
), | ||
], | ||
}); | ||
@@ -393,2 +418,4 @@ return; | ||
function fix(fixer) { return fixer.remove(node); } | ||
if (!node.value) { | ||
@@ -398,3 +425,8 @@ report(context, messages.emptyIsMeaningless, 'emptyIsMeaningless', { | ||
data: { attributeName: attribute }, | ||
fix, | ||
suggest: [ | ||
Object.assign( | ||
getMessageData('suggestRemoveEmpty', messages.suggestRemoveEmpty), | ||
{ fix } | ||
), | ||
], | ||
}); | ||
@@ -420,12 +452,19 @@ return; | ||
data: { attributeName: attribute }, | ||
fix, | ||
suggest: [ | ||
Object.assign( | ||
getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault), | ||
{ fix } | ||
), | ||
], | ||
}); | ||
return; | ||
} | ||
if (node.value.expression.type === 'Identifier' && node.value.expression.name === 'undefined') { | ||
} else if (node.value.expression.type === 'Identifier' && node.value.expression.name === 'undefined') { | ||
report(context, messages.onlyStrings, 'onlyStrings', { | ||
node, | ||
data: { attributeName: attribute }, | ||
fix, | ||
suggest: [ | ||
Object.assign( | ||
getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault), | ||
{ fix } | ||
), | ||
], | ||
}); | ||
@@ -458,7 +497,10 @@ } | ||
}, | ||
suggest: [ | ||
Object.assign( | ||
getMessageData('suggestRemoveInvalid', messages.suggestRemoveInvalid), | ||
{ fix(fixer) { return fixer.replaceText(value, value.raw.replace(value.value, '')); } } | ||
), | ||
], | ||
}); | ||
return; | ||
} | ||
if (!validTagSet.has(node.arguments[0].value)) { | ||
} else if (!validTagSet.has(node.arguments[0].value)) { | ||
report(context, messages.notValidFor, 'notValidFor', { | ||
@@ -471,2 +513,3 @@ node: value, | ||
}, | ||
suggest: false, | ||
}); | ||
@@ -512,2 +555,3 @@ } | ||
}, | ||
suggest: false, | ||
}); | ||
@@ -525,2 +569,3 @@ | ||
}, | ||
suggest: false, | ||
}); | ||
@@ -552,3 +597,2 @@ | ||
meta: { | ||
fixable: 'code', | ||
docs: { | ||
@@ -567,2 +611,4 @@ description: 'Disallow usage of invalid attributes', | ||
}], | ||
type: 'suggestion', | ||
hasSuggestions: true, // eslint-disable-line eslint-plugin/require-meta-has-suggestions | ||
}, | ||
@@ -569,0 +615,0 @@ |
@@ -8,2 +8,4 @@ /** | ||
const values = require('object.values'); | ||
const Components = require('../util/Components'); | ||
@@ -68,11 +70,10 @@ const docsUrl = require('../util/docsUrl'); | ||
const list = components.list(); | ||
Object.keys(list).filter((component) => !isIgnored(list[component])).forEach((component, i) => { | ||
if (i >= 1) { | ||
values(components.list()) | ||
.filter((component) => !isIgnored(component)) | ||
.slice(1) | ||
.forEach((component) => { | ||
report(context, messages.onlyOneComponent, 'onlyOneComponent', { | ||
node: list[component].node, | ||
node: component.node, | ||
}); | ||
} | ||
}); | ||
}); | ||
}, | ||
@@ -79,0 +80,0 @@ }; |
@@ -8,2 +8,4 @@ /** | ||
const values = require('object.values'); | ||
const Components = require('../util/Components'); | ||
@@ -79,6 +81,7 @@ const docsUrl = require('../util/docsUrl'); | ||
'Program:exit'() { | ||
const list = components.list(); | ||
Object.keys(list).filter((component) => !isValid(list[component])).forEach((component) => { | ||
reportSetStateUsages(list[component]); | ||
}); | ||
values(components.list()) | ||
.filter((component) => !isValid(component)) | ||
.forEach((component) => { | ||
reportSetStateUsages(component); | ||
}); | ||
}, | ||
@@ -85,0 +88,0 @@ }; |
@@ -250,7 +250,5 @@ /** | ||
node.properties.forEach((property) => { | ||
if (property.type !== 'SpreadElement') { | ||
reportErrorIfPropertyCasingTypo(property.value, property.key, false); | ||
reportErrorIfLifecycleMethodCasingTypo(property); | ||
} | ||
node.properties.filter((property) => property.type !== 'SpreadElement').forEach((property) => { | ||
reportErrorIfPropertyCasingTypo(property.value, property.key, false); | ||
reportErrorIfLifecycleMethodCasingTypo(property); | ||
}); | ||
@@ -257,0 +255,0 @@ }, |
@@ -55,3 +55,3 @@ /** | ||
items: { | ||
oneOf: [{ | ||
anyOf: [{ | ||
type: 'string', | ||
@@ -58,0 +58,0 @@ }, { |
@@ -45,2 +45,3 @@ /** | ||
'line', | ||
'marker', | ||
'mask', | ||
@@ -206,3 +207,3 @@ 'path', | ||
// See https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes | ||
'dir', 'draggable', 'hidden', 'id', 'lang', 'nonce', 'part', 'slot', 'style', 'title', 'translate', | ||
'dir', 'draggable', 'hidden', 'id', 'lang', 'nonce', 'part', 'slot', 'style', 'title', 'translate', 'inert', | ||
// Element specific attributes | ||
@@ -549,3 +550,3 @@ // See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes (includes global attributes too) | ||
if (tagName === 'fbt') { return; } // fbt nodes are bonkers, let's not go there | ||
if (tagName === 'fbt' || tagName === 'fbs') { return; } // fbt/fbs nodes are bonkers, let's not go there | ||
@@ -552,0 +553,0 @@ if (!isValidHTMLTagInJSX(node)) { return; } |
@@ -248,7 +248,7 @@ /** | ||
if (node.init && isThisExpression(node.init) && node.id.type === 'ObjectPattern') { | ||
node.id.properties.forEach((prop) => { | ||
if (prop.type === 'Property' && isKeyLiteralLike(prop, prop.key)) { | ||
node.id.properties | ||
.filter((prop) => prop.type === 'Property' && isKeyLiteralLike(prop, prop.key)) | ||
.forEach((prop) => { | ||
addUsedProperty(prop.key); | ||
} | ||
}); | ||
}); | ||
} | ||
@@ -255,0 +255,0 @@ }, |
@@ -8,2 +8,4 @@ /** | ||
const values = require('object.values'); | ||
// As for exceptions for props.children or props.className (and alike) look at | ||
@@ -163,10 +165,8 @@ // https://github.com/jsx-eslint/eslint-plugin-react/issues/7 | ||
'Program:exit'() { | ||
const list = components.list(); | ||
// Report undeclared proptypes for all classes | ||
Object.keys(list).filter((component) => mustBeValidated(list[component])).forEach((component) => { | ||
if (!mustBeValidated(list[component])) { | ||
return; | ||
} | ||
reportUnusedPropTypes(list[component]); | ||
}); | ||
values(components.list()) | ||
.filter((component) => mustBeValidated(component)) | ||
.forEach((component) => { | ||
reportUnusedPropTypes(component); | ||
}); | ||
}, | ||
@@ -173,0 +173,0 @@ }; |
@@ -8,2 +8,5 @@ /** | ||
const flatMap = require('array.prototype.flatmap'); | ||
const values = require('object.values'); | ||
const Components = require('../util/Components'); | ||
@@ -53,14 +56,9 @@ const docsUrl = require('../util/docsUrl'); | ||
'Program:exit'() { | ||
const list = components.list(); | ||
flatMap( | ||
values(components.list()), | ||
(component) => component.declaredPropTypes || [] | ||
).forEach((declaredPropTypes) => { | ||
Object.keys(declaredPropTypes).forEach((propName) => { | ||
const prop = declaredPropTypes[propName]; | ||
Object.keys(list).forEach((key) => { | ||
const component = list[key]; | ||
if (!component.declaredPropTypes) { | ||
return; | ||
} | ||
Object.keys(component.declaredPropTypes).forEach((propName) => { | ||
const prop = component.declaredPropTypes[propName]; | ||
if (!prop.node || !isFlowPropertyType(prop.node)) { | ||
@@ -67,0 +65,0 @@ return; |
@@ -10,2 +10,4 @@ /** | ||
const values = require('object.values'); | ||
const Components = require('../util/Components'); | ||
@@ -367,25 +369,21 @@ const testReactVersion = require('../util/version').testReactVersion; | ||
const list = components.list(); | ||
Object.keys(list).forEach((component) => { | ||
if ( | ||
hasOtherProperties(list[component].node) | ||
|| list[component].useThis | ||
|| list[component].useRef | ||
|| list[component].invalidReturn | ||
|| list[component].hasChildContextTypes | ||
|| list[component].useDecorators | ||
|| ( | ||
!componentUtil.isES5Component(list[component].node, context) | ||
&& !componentUtil.isES6Component(list[component].node, context) | ||
values(list) | ||
.filter((component) => ( | ||
!hasOtherProperties(component.node) | ||
&& !component.useThis | ||
&& !component.useRef | ||
&& !component.invalidReturn | ||
&& !component.hasChildContextTypes | ||
&& !component.useDecorators | ||
&& !component.hasSCU | ||
&& ( | ||
componentUtil.isES5Component(component.node, context) | ||
|| componentUtil.isES6Component(component.node, context) | ||
) | ||
) { | ||
return; | ||
} | ||
if (list[component].hasSCU) { | ||
return; | ||
} | ||
report(context, messages.componentShouldBePure, 'componentShouldBePure', { | ||
node: list[component].node, | ||
)) | ||
.forEach((component) => { | ||
report(context, messages.componentShouldBePure, 'componentShouldBePure', { | ||
node: component.node, | ||
}); | ||
}); | ||
}); | ||
}, | ||
@@ -392,0 +390,0 @@ }; |
@@ -11,2 +11,4 @@ /** | ||
const values = require('object.values'); | ||
const Components = require('../util/Components'); | ||
@@ -192,5 +194,7 @@ const docsUrl = require('../util/docsUrl'); | ||
// Report undeclared proptypes for all classes | ||
Object.keys(list).filter((component) => mustBeValidated(list[component])).forEach((component) => { | ||
reportUndeclaredPropTypes(list[component]); | ||
}); | ||
values(list) | ||
.filter((component) => mustBeValidated(component)) | ||
.forEach((component) => { | ||
reportUndeclaredPropTypes(component); | ||
}); | ||
}, | ||
@@ -197,0 +201,0 @@ }; |
@@ -8,2 +8,4 @@ /** | ||
const values = require('object.values'); | ||
const Components = require('../util/Components'); | ||
@@ -229,8 +231,8 @@ const componentUtil = require('../util/componentUtil'); | ||
'Program:exit'() { | ||
const list = components.list(); | ||
// Report missing shouldComponentUpdate for all components | ||
Object.keys(list).filter((component) => !list[component].hasSCU).forEach((component) => { | ||
reportMissingOptimization(list[component]); | ||
}); | ||
values(components.list()) | ||
.filter((component) => !component.hasSCU) | ||
.forEach((component) => { | ||
reportMissingOptimization(component); | ||
}); | ||
}, | ||
@@ -237,0 +239,0 @@ }; |
@@ -8,2 +8,4 @@ /** | ||
const values = require('object.values'); | ||
const Components = require('../util/Components'); | ||
@@ -86,18 +88,16 @@ const astUtil = require('../util/ast'); | ||
'Program:exit'() { | ||
const list = components.list(); | ||
Object.keys(list).forEach((component) => { | ||
if ( | ||
!findRenderMethod(list[component].node) | ||
|| list[component].hasReturnStatement | ||
|| ( | ||
!componentUtil.isES5Component(list[component].node, context) | ||
&& !componentUtil.isES6Component(list[component].node, context) | ||
values(components.list()) | ||
.filter((component) => ( | ||
findRenderMethod(component.node) | ||
&& !component.hasReturnStatement | ||
&& ( | ||
componentUtil.isES5Component(component.node, context) | ||
|| componentUtil.isES6Component(component.node, context) | ||
) | ||
) { | ||
return; | ||
} | ||
report(context, messages.noRenderReturn, 'noRenderReturn', { | ||
node: findRenderMethod(list[component].node), | ||
)) | ||
.forEach((component) => { | ||
report(context, messages.noRenderReturn, 'noRenderReturn', { | ||
node: findRenderMethod(component.node), | ||
}); | ||
}); | ||
}); | ||
}, | ||
@@ -104,0 +104,0 @@ }; |
@@ -10,2 +10,3 @@ /** | ||
const entries = require('object.entries'); | ||
const values = require('object.values'); | ||
const arrayIncludes = require('array-includes'); | ||
@@ -435,5 +436,4 @@ | ||
'Program:exit'() { | ||
const list = components.list(); | ||
Object.keys(list).forEach((component) => { | ||
const properties = astUtil.getComponentProperties(list[component].node); | ||
values(components.list()).forEach((component) => { | ||
const properties = astUtil.getComponentProperties(component.node); | ||
checkPropsOrder(properties); | ||
@@ -440,0 +440,0 @@ }); |
@@ -11,3 +11,3 @@ /** | ||
const propWrapperUtil = require('../util/propWrapper'); | ||
// const propTypesSortUtil = require('../util/propTypesSort'); | ||
const propTypesSortUtil = require('../util/propTypesSort'); | ||
const report = require('../util/report'); | ||
@@ -33,3 +33,3 @@ | ||
}, | ||
// fixable: 'code', | ||
fixable: 'code', | ||
@@ -111,13 +111,13 @@ messages, | ||
// function fix(fixer) { | ||
// return propTypesSortUtil.fixPropTypesSort( | ||
// fixer, | ||
// context, | ||
// declarations, | ||
// ignoreCase, | ||
// requiredFirst, | ||
// callbacksLast, | ||
// sortShapeProp | ||
// ); | ||
// } | ||
function fix(fixer) { | ||
return propTypesSortUtil.fixPropTypesSort( | ||
fixer, | ||
context, | ||
declarations, | ||
ignoreCase, | ||
requiredFirst, | ||
callbacksLast, | ||
sortShapeProp | ||
); | ||
} | ||
@@ -156,3 +156,3 @@ const callbackPropsLastSeen = new WeakSet(); | ||
node: curr, | ||
// fix | ||
fix, | ||
}); | ||
@@ -175,3 +175,3 @@ } | ||
node: prev, | ||
// fix | ||
fix, | ||
}); | ||
@@ -188,3 +188,3 @@ } | ||
node: curr, | ||
// fix | ||
fix, | ||
}); | ||
@@ -269,5 +269,4 @@ } | ||
}, | ||
}; | ||
}, | ||
}; |
@@ -112,3 +112,3 @@ /** | ||
let component = list[getId(node)]; | ||
while (!component) { | ||
while (!component || component.confidence < 1) { | ||
node = node.parent; | ||
@@ -185,3 +185,3 @@ if (!node) { | ||
const list = Lists.get(this); | ||
return Object.keys(list).filter((i) => list[i].confidence >= 2).length; | ||
return values(list).filter((component) => component.confidence >= 2).length; | ||
} | ||
@@ -482,3 +482,2 @@ | ||
if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') { | ||
const isMethod = parent.type === 'Property' && parent.method; | ||
const isPropertyAssignment = parent.type === 'AssignmentExpression' | ||
@@ -568,2 +567,14 @@ && parent.left.type === 'MemberExpression'; | ||
if ( | ||
node.parent.type === 'Property' && ( | ||
(node.parent.method && !node.parent.computed) // case: { f() { return ... } } | ||
|| (!node.id && !node.parent.computed) // case: { f: () => ... } | ||
) | ||
) { | ||
if (isFirstLetterCapitalized(node.parent.key.name) && utils.isReturningJSX(node)) { | ||
return node; | ||
} | ||
return undefined; | ||
} | ||
// Case like `React.memo(() => <></>)` or `React.forwardRef(...)` | ||
@@ -583,6 +594,2 @@ const pragmaComponentWrapper = utils.getPragmaComponentWrapper(node); | ||
if (isMethod && !isFirstLetterCapitalized(node.parent.key.name)) { | ||
return utils.isReturningJSX(node) ? node : undefined; | ||
} | ||
if (node.id) { | ||
@@ -861,9 +868,4 @@ return isFirstLetterCapitalized(node.id.name) ? node : undefined; | ||
const 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); | ||
const component = utils.getStatelessComponent(node); | ||
if (!component) { | ||
return; | ||
@@ -880,3 +882,3 @@ } | ||
node = utils.getParentComponent(); | ||
node = utils.getStatelessComponent(node); | ||
if (!node) { | ||
@@ -894,9 +896,4 @@ return; | ||
const 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); | ||
const component = utils.getStatelessComponent(node); | ||
if (!component) { | ||
return; | ||
@@ -903,0 +900,0 @@ } |
@@ -7,2 +7,4 @@ /** | ||
const toSorted = require('array.prototype.tosorted'); | ||
const astUtil = require('./ast'); | ||
@@ -120,5 +122,33 @@ | ||
*/ | ||
const commentnodeMap = new WeakMap(); // all nodes reference WeakMap for start and end range | ||
function fixPropTypesSort(fixer, context, declarations, ignoreCase, requiredFirst, callbacksLast, sortShapeProp) { | ||
function sortInSource(allNodes, source) { | ||
const originalSource = source; | ||
const sourceCode = context.getSourceCode(); | ||
for (let i = 0; i < allNodes.length; i++) { | ||
const node = allNodes[i]; | ||
let commentAfter = []; | ||
let commentBefore = []; | ||
let newStart = 0; | ||
let newEnd = 0; | ||
try { | ||
commentBefore = sourceCode.getCommentsBefore(node); | ||
commentAfter = sourceCode.getCommentsAfter(node); | ||
} catch (e) { /**/ } | ||
if (commentAfter.length === 0 || commentBefore.length === 0) { | ||
newStart = node.range[0]; | ||
newEnd = node.range[1]; | ||
} | ||
const firstCommentBefore = commentBefore[0]; | ||
if (commentBefore.length >= 1) { | ||
newStart = firstCommentBefore.range[0]; | ||
} | ||
const lastCommentAfter = commentAfter[commentAfter.length - 1]; | ||
if (commentAfter.length >= 1) { | ||
newEnd = lastCommentAfter.range[1]; | ||
} | ||
commentnodeMap.set(node, { start: newStart, end: newEnd, hasComment: true }); | ||
} | ||
const nodeGroups = allNodes.reduce((acc, curr) => { | ||
@@ -134,9 +164,14 @@ if (curr.type === 'ExperimentalSpreadProperty' || curr.type === 'SpreadElement') { | ||
nodeGroups.forEach((nodes) => { | ||
const sortedAttributes = nodes | ||
.slice() | ||
.sort((a, b) => sorter(a, b, context, ignoreCase, requiredFirst, callbacksLast)); | ||
const sortedAttributes = toSorted( | ||
nodes, | ||
(a, b) => sorter(a, b, context, ignoreCase, requiredFirst, callbacksLast) | ||
); | ||
source = nodes.reduceRight((acc, attr, index) => { | ||
const sortedAttr = sortedAttributes[index]; | ||
let sortedAttrText = context.getSourceCode().getText(sortedAttr); | ||
const sourceCodeText = sourceCode.getText(); | ||
let sortedAttrText = sourceCodeText.substring( | ||
commentnodeMap.get(sortedAttr).start, | ||
commentnodeMap.get(sortedAttr).end | ||
); | ||
if (sortShapeProp && isShapeProp(sortedAttr.value)) { | ||
@@ -152,3 +187,3 @@ const shape = getShapeProperties(sortedAttr.value); | ||
} | ||
return `${acc.slice(0, attr.range[0])}${sortedAttrText}${acc.slice(attr.range[1])}`; | ||
return `${acc.slice(0, commentnodeMap.get(attr).start)}${sortedAttrText}${acc.slice(commentnodeMap.get(attr).end)}`; | ||
}, source); | ||
@@ -161,4 +196,4 @@ }); | ||
const rangeStart = declarations[0].range[0]; | ||
const rangeEnd = declarations[declarations.length - 1].range[1]; | ||
const rangeStart = commentnodeMap.get(declarations[0]).start; | ||
const rangeEnd = commentnodeMap.get(declarations[declarations.length - 1]).end; | ||
return fixer.replaceTextRange([rangeStart, rangeEnd], source.slice(rangeStart, rangeEnd)); | ||
@@ -165,0 +200,0 @@ } |
'use strict'; | ||
const semver = require('semver'); | ||
const eslintPkg = require('eslint/package.json'); | ||
const getMessageData = require('./message'); | ||
@@ -9,3 +8,3 @@ module.exports = function report(context, message, messageId, data) { | ||
Object.assign( | ||
messageId && semver.satisfies(eslintPkg.version, '>= 4.15') ? { messageId } : { message }, | ||
getMessageData(messageId, message), | ||
data | ||
@@ -12,0 +11,0 @@ ) |
@@ -7,2 +7,4 @@ /** | ||
const values = require('object.values'); | ||
const astUtil = require('./ast'); | ||
@@ -561,9 +563,9 @@ const componentUtil = require('./componentUtil'); | ||
'Program:exit'() { | ||
const list = components.list(); | ||
Object.keys(list).filter((component) => mustBeValidated(list[component])).forEach((component) => { | ||
handleCustomValidators(list[component]); | ||
}); | ||
values(components.list()) | ||
.filter((component) => mustBeValidated(component)) | ||
.forEach((component) => { | ||
handleCustomValidators(component); | ||
}); | ||
}, | ||
}; | ||
}; |
{ | ||
"name": "eslint-plugin-react", | ||
"version": "7.31.10", | ||
"version": "7.32.2", | ||
"author": "Yannick Croissant <yannick.croissant+npm@gmail.com>", | ||
@@ -11,2 +11,3 @@ "description": "React specific linting rules for ESLint", | ||
"lint:docs": "markdownlint \"**/*.md\"", | ||
"postlint:docs": "npm run update:eslint-docs -- --check", | ||
"lint": "eslint .", | ||
@@ -19,4 +20,3 @@ "postlint": "npm run type-check", | ||
"unit-test": "istanbul cover node_modules/mocha/bin/_mocha tests/lib/**/*.js tests/util/**/*.js tests/index.js", | ||
"generate-list-of-rules": "md-magic --path README.md", | ||
"generate-list-of-rules:check": "npm run generate-list-of-rules && git diff --exit-code README.md" | ||
"update:eslint-docs": "eslint-doc-generator" | ||
}, | ||
@@ -30,4 +30,5 @@ "repository": { | ||
"dependencies": { | ||
"array-includes": "^3.1.5", | ||
"array.prototype.flatmap": "^1.3.0", | ||
"array-includes": "^3.1.6", | ||
"array.prototype.flatmap": "^1.3.1", | ||
"array.prototype.tosorted": "^1.1.1", | ||
"doctrine": "^2.1.0", | ||
@@ -37,13 +38,13 @@ "estraverse": "^5.3.0", | ||
"minimatch": "^3.1.2", | ||
"object.entries": "^1.1.5", | ||
"object.fromentries": "^2.0.5", | ||
"object.hasown": "^1.1.1", | ||
"object.values": "^1.1.5", | ||
"object.entries": "^1.1.6", | ||
"object.fromentries": "^2.0.6", | ||
"object.hasown": "^1.1.2", | ||
"object.values": "^1.1.6", | ||
"prop-types": "^15.8.1", | ||
"resolve": "^2.0.0-next.3", | ||
"resolve": "^2.0.0-next.4", | ||
"semver": "^6.3.0", | ||
"string.prototype.matchall": "^4.0.7" | ||
"string.prototype.matchall": "^4.0.8" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.19.3", | ||
"@babel/core": "^7.20.12", | ||
"@babel/eslint-parser": "^7.19.1", | ||
@@ -58,15 +59,15 @@ "@babel/plugin-syntax-decorators": "^7.19.0", | ||
"@typescript-eslint/parser": "^2.34.0 || ^3.10.1 || ^4.0.0 || ^5.0.0", | ||
"aud": "^2.0.1", | ||
"aud": "^2.0.2", | ||
"babel-eslint": "^8 || ^9 || ^10.1.0", | ||
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8", | ||
"eslint-config-airbnb-base": "^15.0.0", | ||
"eslint-doc-generator": "^1.4.2", | ||
"eslint-plugin-eslint-plugin": "^2.3.0 || ^3.5.3 || ^4.0.1 || ^5.0.5", | ||
"eslint-plugin-import": "^2.26.0", | ||
"eslint-remote-tester": "^3.0.0", | ||
"eslint-remote-tester-repositories": "^0.0.7", | ||
"eslint-remote-tester-repositories": "^1.0.0", | ||
"eslint-scope": "^3.7.3", | ||
"espree": "^3.5.4", | ||
"istanbul": "^0.4.5", | ||
"ls-engines": "^0.7.0", | ||
"markdown-magic": "^2.6.1", | ||
"ls-engines": "^0.8.0", | ||
"markdownlint-cli": "^0.8.0 || ^0.32.2", | ||
@@ -101,2 +102,3 @@ "mocha": "^5.2.0", | ||
"*.config.js", | ||
".eslint-doc-generatorrc.js", | ||
".eslintrc", | ||
@@ -103,0 +105,0 @@ ".editorconfig", |
391
README.md
@@ -18,5 +18,5 @@ # `eslint-plugin-react` <sup>[![Version Badge][npm-version-svg]][package-url]</sup> | ||
It is also possible to install ESLint globally rather than locally (using npm install eslint --global). However, this is not recommended, and any plugins or shareable configs that you use must be installed locally in either case. | ||
It is also possible to install ESLint globally rather than locally (using `npm install -g eslint`). However, this is not recommended, and any plugins or shareable configs that you use must be installed locally in either case. | ||
## Configuration | ||
## Configuration (legacy: `.eslintrc*`) | ||
@@ -113,128 +113,6 @@ Use [our preset](#recommended) to get reasonable defaults: | ||
## List of supported rules | ||
### Shareable configs | ||
β: Enabled in the [`recommended`](#recommended) configuration.\ | ||
π§: Fixable with [`eslint --fix`](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems).\ | ||
π‘: Provides editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). | ||
#### Recommended | ||
<!-- AUTO-GENERATED-CONTENT:START (BASIC_RULES) --> | ||
| β | π§ | π‘ | Rule | Description | | ||
| :---: | :---: | :---: | :--- | :--- | | ||
| | | | [react/boolean-prop-naming](docs/rules/boolean-prop-naming.md) | Enforces consistent naming for boolean props | | ||
| | | | [react/button-has-type](docs/rules/button-has-type.md) | Disallow usage of `button` elements without an explicit `type` attribute | | ||
| | | | [react/default-props-match-prop-types](docs/rules/default-props-match-prop-types.md) | Enforce all defaultProps have a corresponding non-required PropType | | ||
| | π§ | | [react/destructuring-assignment](docs/rules/destructuring-assignment.md) | Enforce consistent usage of destructuring assignment of props, state, and context | | ||
| β | | | [react/display-name](docs/rules/display-name.md) | Disallow missing displayName in a React component definition | | ||
| | | | [react/forbid-component-props](docs/rules/forbid-component-props.md) | Disallow certain props on components | | ||
| | | | [react/forbid-dom-props](docs/rules/forbid-dom-props.md) | Disallow certain props on DOM Nodes | | ||
| | | | [react/forbid-elements](docs/rules/forbid-elements.md) | Disallow certain elements | | ||
| | | | [react/forbid-foreign-prop-types](docs/rules/forbid-foreign-prop-types.md) | Disallow using another component's propTypes | | ||
| | | | [react/forbid-prop-types](docs/rules/forbid-prop-types.md) | Disallow certain propTypes | | ||
| | π§ | | [react/function-component-definition](docs/rules/function-component-definition.md) | Enforce a specific function type for function components | | ||
| | | π‘ | [react/hook-use-state](docs/rules/hook-use-state.md) | Ensure destructuring and symmetric naming of useState hook value and setter variables | | ||
| | | | [react/iframe-missing-sandbox](docs/rules/iframe-missing-sandbox.md) | Enforce sandbox attribute on iframe elements | | ||
| | | | [react/no-access-state-in-setstate](docs/rules/no-access-state-in-setstate.md) | Disallow when this.state is accessed within setState | | ||
| | | | [react/no-adjacent-inline-elements](docs/rules/no-adjacent-inline-elements.md) | Disallow adjacent inline elements not separated by whitespace. | | ||
| | | | [react/no-array-index-key](docs/rules/no-array-index-key.md) | Disallow usage of Array index in keys | | ||
| | π§ | | [react/no-arrow-function-lifecycle](docs/rules/no-arrow-function-lifecycle.md) | Lifecycle methods should be methods on the prototype, not class fields | | ||
| β | | | [react/no-children-prop](docs/rules/no-children-prop.md) | Disallow passing of children as props | | ||
| | | | [react/no-danger](docs/rules/no-danger.md) | Disallow usage of dangerous JSX properties | | ||
| β | | | [react/no-danger-with-children](docs/rules/no-danger-with-children.md) | Disallow when a DOM element is using both children and dangerouslySetInnerHTML | | ||
| β | | | [react/no-deprecated](docs/rules/no-deprecated.md) | Disallow usage of deprecated methods | | ||
| | | | [react/no-did-mount-set-state](docs/rules/no-did-mount-set-state.md) | Disallow usage of setState in componentDidMount | | ||
| | | | [react/no-did-update-set-state](docs/rules/no-did-update-set-state.md) | Disallow usage of setState in componentDidUpdate | | ||
| β | | | [react/no-direct-mutation-state](docs/rules/no-direct-mutation-state.md) | Disallow direct mutation of this.state | | ||
| β | | | [react/no-find-dom-node](docs/rules/no-find-dom-node.md) | Disallow usage of findDOMNode | | ||
| | π§ | | [react/no-invalid-html-attribute](docs/rules/no-invalid-html-attribute.md) | Disallow usage of invalid attributes | | ||
| β | | | [react/no-is-mounted](docs/rules/no-is-mounted.md) | Disallow usage of isMounted | | ||
| | | | [react/no-multi-comp](docs/rules/no-multi-comp.md) | Disallow multiple component definition per file | | ||
| | | | [react/no-namespace](docs/rules/no-namespace.md) | Enforce that namespaces are not used in React elements | | ||
| | | | [react/no-redundant-should-component-update](docs/rules/no-redundant-should-component-update.md) | Disallow usage of shouldComponentUpdate when extending React.PureComponent | | ||
| β | | | [react/no-render-return-value](docs/rules/no-render-return-value.md) | Disallow usage of the return value of ReactDOM.render | | ||
| | | | [react/no-set-state](docs/rules/no-set-state.md) | Disallow usage of setState | | ||
| β | | | [react/no-string-refs](docs/rules/no-string-refs.md) | Disallow using string references | | ||
| | | | [react/no-this-in-sfc](docs/rules/no-this-in-sfc.md) | Disallow `this` from being used in stateless functional components | | ||
| | | | [react/no-typos](docs/rules/no-typos.md) | Disallow common typos | | ||
| β | | | [react/no-unescaped-entities](docs/rules/no-unescaped-entities.md) | Disallow unescaped HTML entities from appearing in markup | | ||
| β | π§ | | [react/no-unknown-property](docs/rules/no-unknown-property.md) | Disallow usage of unknown DOM property | | ||
| | | | [react/no-unsafe](docs/rules/no-unsafe.md) | Disallow usage of unsafe lifecycle methods | | ||
| | | | [react/no-unstable-nested-components](docs/rules/no-unstable-nested-components.md) | Disallow creating unstable components inside components | | ||
| | | | [react/no-unused-class-component-methods](docs/rules/no-unused-class-component-methods.md) | Disallow declaring unused methods of component class | | ||
| | | | [react/no-unused-prop-types](docs/rules/no-unused-prop-types.md) | Disallow definitions of unused propTypes | | ||
| | | | [react/no-unused-state](docs/rules/no-unused-state.md) | Disallow definitions of unused state | | ||
| | | | [react/no-will-update-set-state](docs/rules/no-will-update-set-state.md) | Disallow 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-exact-props](docs/rules/prefer-exact-props.md) | Prefer exact proptype definitions | | ||
| | π§ | | [react/prefer-read-only-props](docs/rules/prefer-read-only-props.md) | Enforce that props are read-only | | ||
| | | | [react/prefer-stateless-function](docs/rules/prefer-stateless-function.md) | Enforce stateless components to be written as a pure function | | ||
| β | | | [react/prop-types](docs/rules/prop-types.md) | Disallow missing props validation in a React component definition | | ||
| β | | | [react/react-in-jsx-scope](docs/rules/react-in-jsx-scope.md) | Disallow 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) | Disallow extra closing tags for components without children | | ||
| | | | [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/state-in-constructor](docs/rules/state-in-constructor.md) | Enforce class component state initialization style | | ||
| | | | [react/static-property-placement](docs/rules/static-property-placement.md) | Enforces where React component static properties should be positioned. | | ||
| | | | [react/style-prop-object](docs/rules/style-prop-object.md) | Enforce style prop value is an object | | ||
| | | | [react/void-dom-elements-no-children](docs/rules/void-dom-elements-no-children.md) | Disallow void DOM elements (e.g. `<img />`, `<br />`) from receiving children | | ||
<!-- AUTO-GENERATED-CONTENT:END --> | ||
### JSX-specific rules | ||
<!-- AUTO-GENERATED-CONTENT:START (JSX_RULES) --> | ||
| β | π§ | π‘ | Rule | Description | | ||
| :---: | :---: | :---: | :--- | :--- | | ||
| | π§ | | [react/jsx-boolean-value](docs/rules/jsx-boolean-value.md) | Enforce boolean attributes notation in JSX | | ||
| | | | [react/jsx-child-element-spacing](docs/rules/jsx-child-element-spacing.md) | Enforce or disallow spaces inside of curly braces in JSX attributes and expressions | | ||
| | π§ | | [react/jsx-closing-bracket-location](docs/rules/jsx-closing-bracket-location.md) | Enforce closing bracket location in JSX | | ||
| | π§ | | [react/jsx-closing-tag-location](docs/rules/jsx-closing-tag-location.md) | Enforce closing tag location for multiline JSX | | ||
| | π§ | | [react/jsx-curly-brace-presence](docs/rules/jsx-curly-brace-presence.md) | Disallow unnecessary JSX expressions when literals alone are sufficient or enforce JSX expressions on literals in JSX children or attributes | | ||
| | π§ | | [react/jsx-curly-newline](docs/rules/jsx-curly-newline.md) | Enforce consistent linebreaks in curly braces in JSX attributes and expressions | | ||
| | π§ | | [react/jsx-curly-spacing](docs/rules/jsx-curly-spacing.md) | Enforce or disallow spaces inside of curly braces in JSX attributes and expressions | | ||
| | π§ | | [react/jsx-equals-spacing](docs/rules/jsx-equals-spacing.md) | Enforce or disallow spaces around equal signs in JSX attributes | | ||
| | | | [react/jsx-filename-extension](docs/rules/jsx-filename-extension.md) | Disallow file extensions that may contain JSX | | ||
| | π§ | | [react/jsx-first-prop-new-line](docs/rules/jsx-first-prop-new-line.md) | Enforce proper position of the first property in JSX | | ||
| | π§ | | [react/jsx-fragments](docs/rules/jsx-fragments.md) | Enforce shorthand or standard form for React fragments | | ||
| | | | [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) | Enforce JSX indentation | | ||
| | π§ | | [react/jsx-indent-props](docs/rules/jsx-indent-props.md) | Enforce props indentation in JSX | | ||
| β | | | [react/jsx-key](docs/rules/jsx-key.md) | Disallow missing `key` props in iterators/collection literals | | ||
| | | | [react/jsx-max-depth](docs/rules/jsx-max-depth.md) | Enforce JSX maximum depth | | ||
| | π§ | | [react/jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md) | Enforce maximum of props on a single line in JSX | | ||
| | π§ | | [react/jsx-newline](docs/rules/jsx-newline.md) | Require or prevent a new line after jsx elements and expressions. | | ||
| | | | [react/jsx-no-bind](docs/rules/jsx-no-bind.md) | Disallow `.bind()` or arrow functions in JSX props | | ||
| β | | | [react/jsx-no-comment-textnodes](docs/rules/jsx-no-comment-textnodes.md) | Disallow comments from being inserted as text nodes | | ||
| | | | [react/jsx-no-constructed-context-values](docs/rules/jsx-no-constructed-context-values.md) | Disallows JSX context provider values from taking values that will cause needless rerenders | | ||
| β | | | [react/jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md) | Disallow duplicate properties in JSX | | ||
| | π§ | | [react/jsx-no-leaked-render](docs/rules/jsx-no-leaked-render.md) | Disallow problematic leaked values from being rendered | | ||
| | | | [react/jsx-no-literals](docs/rules/jsx-no-literals.md) | Disallow usage of string literals in JSX | | ||
| | | | [react/jsx-no-script-url](docs/rules/jsx-no-script-url.md) | Disallow usage of `javascript:` URLs | | ||
| β | π§ | | [react/jsx-no-target-blank](docs/rules/jsx-no-target-blank.md) | Disallow `target="_blank"` attribute without `rel="noreferrer"` | | ||
| β | | | [react/jsx-no-undef](docs/rules/jsx-no-undef.md) | Disallow undeclared variables in JSX | | ||
| | π§ | | [react/jsx-no-useless-fragment](docs/rules/jsx-no-useless-fragment.md) | Disallow unnecessary fragments | | ||
| | π§ | | [react/jsx-one-expression-per-line](docs/rules/jsx-one-expression-per-line.md) | Require one JSX element per line | | ||
| | | | [react/jsx-pascal-case](docs/rules/jsx-pascal-case.md) | Enforce PascalCase for user-defined JSX components | | ||
| | π§ | | [react/jsx-props-no-multi-spaces](docs/rules/jsx-props-no-multi-spaces.md) | Disallow multiple spaces between inline JSX props | | ||
| | | | [react/jsx-props-no-spreading](docs/rules/jsx-props-no-spreading.md) | Disallow JSX prop spreading | | ||
| | | | [react/jsx-sort-default-props](docs/rules/jsx-sort-default-props.md) | Enforce defaultProps declarations alphabetical sorting | | ||
| | π§ | | [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) | Enforce spacing before closing bracket in JSX. β This rule is deprecated. | | ||
| | π§ | | [react/jsx-tag-spacing](docs/rules/jsx-tag-spacing.md) | Enforce whitespace in and around the JSX opening and closing brackets | | ||
| β | | | [react/jsx-uses-react](docs/rules/jsx-uses-react.md) | Disallow React to be incorrectly marked as unused | | ||
| β | | | [react/jsx-uses-vars](docs/rules/jsx-uses-vars.md) | Disallow variables used in JSX to be incorrectly marked as unused | | ||
| | π§ | | [react/jsx-wrap-multilines](docs/rules/jsx-wrap-multilines.md) | Disallow missing parentheses around multiline JSX | | ||
<!-- AUTO-GENERATED-CONTENT:END --> | ||
### Other useful plugins | ||
- Rules of Hooks: [eslint-plugin-react-hooks](https://github.com/facebook/react/tree/master/packages/eslint-plugin-react-hooks) | ||
- 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 enforces React good practices. | ||
@@ -252,3 +130,3 @@ | ||
### All | ||
#### All | ||
@@ -269,2 +147,261 @@ This plugin also exports an `all` configuration that includes every available rule. | ||
## Configuration (new: `eslint.config.js`) | ||
From [`v8.21.0`](https://github.com/eslint/eslint/releases/tag/v8.21.0), eslint announced a new config system. | ||
In the new system, `.eslintrc*` is no longer used. `eslint.config.js` would be the default config file name. | ||
In eslint `v8`, the legacy system (`.eslintrc*`) would still be supported, while in eslint `v9`, only the new system would be supported. | ||
And from [`v8.23.0`](https://github.com/eslint/eslint/releases/tag/v8.23.0), eslint CLI starts to look up `eslint.config.js`. | ||
**So, if your eslint is `>=8.23.0`, you're 100% ready to use the new config system.** | ||
You might want to check out the official blog posts, | ||
- <https://eslint.org/blog/2022/08/new-config-system-part-1/> | ||
- <https://eslint.org/blog/2022/08/new-config-system-part-2/> | ||
- <https://eslint.org/blog/2022/08/new-config-system-part-3/> | ||
and the [official docs](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new). | ||
### Plugin | ||
The default export of `eslint-plugin-react` is a plugin object. | ||
```js | ||
const react = require('eslint-plugin-react'); | ||
const globals = require('globals'); | ||
module.exports = [ | ||
β¦ | ||
{ | ||
files: ['**/*.{js,jsx,mjs,cjs,ts,tsx}'], | ||
plugins: { | ||
react, | ||
}, | ||
languageOptions: { | ||
parserOptions: { | ||
ecmaFeatures: { | ||
jsx: true, | ||
}, | ||
}, | ||
globals: { | ||
...globals.browser, | ||
}, | ||
}, | ||
rules: { | ||
// ... any rules you want | ||
'react/jsx-uses-react': 'error', | ||
'react/jsx-uses-vars': 'error', | ||
}, | ||
// ... others are omitted for brevity | ||
}, | ||
β¦ | ||
]; | ||
``` | ||
### Configuring shared settings | ||
Refer to the [official docs](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new#configuring-shared-settings). | ||
The schema of the `settings.react` object would be identical to that of what's already described above in the legacy config section. | ||
<!-- markdownlint-disable-next-line no-duplicate-heading --> | ||
### Shareable configs | ||
There're also 3 shareable configs. | ||
- `eslint-plugin-react/configs/all` | ||
- `eslint-plugin-react/configs/recommended` | ||
- `eslint-plugin-react/configs/jsx-runtime` | ||
If your eslint.config.js is ESM, include the `.js` extension (e.g. `eslint-plugin-react/recommended.js`). Note that the next semver-major will require omitting the extension for these imports. | ||
**Note**: These configurations will import `eslint-plugin-react` and enable JSX in [`languageOptions.parserOptions`](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new#configuration-objects). | ||
In the new config system, `plugin:` protocol(e.g. `plugin:react/recommended`) is no longer valid. | ||
As eslint does not automatically import the preset config (shareable config), you explicitly do it by yourself. | ||
```js | ||
const reactRecommended = require('eslint-plugin-react/configs/recommended'); | ||
module.exports = [ | ||
β¦ | ||
reactRecommended, // This is not a plugin object, but a shareable config object | ||
β¦ | ||
]; | ||
``` | ||
You can of course add/override some properties. | ||
**Note**: Our shareable configs does not preconfigure `files` or [`languageOptions.globals`](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new#configuration-objects). | ||
For most of the cases, you probably want to configure some properties by yourself. | ||
```js | ||
const reactRecommended = require('eslint-plugin-react/configs/recommended'); | ||
const globals = require('globals'); | ||
module.exports = [ | ||
β¦ | ||
{ | ||
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], | ||
...reactRecommended, | ||
languageOptions: { | ||
...reactRecommended.languageOptions, | ||
globals: { | ||
...globals.serviceworker, | ||
...globals.browser; | ||
}, | ||
}, | ||
}, | ||
β¦ | ||
]; | ||
``` | ||
The above example is same as the example below, as the new config system is based on chaining. | ||
```js | ||
const reactRecommended = require('eslint-plugin-react/configs/recommended'); | ||
const globals = require('globals'); | ||
module.exports = [ | ||
β¦ | ||
{ | ||
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], | ||
...reactRecommended, | ||
}, | ||
{ | ||
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'], | ||
languageOptions: { | ||
globals: { | ||
...globals.serviceworker, | ||
...globals.browser, | ||
}, | ||
}, | ||
}, | ||
β¦ | ||
]; | ||
``` | ||
## List of supported rules | ||
<!-- begin auto-generated rules list --> | ||
πΌ [Configurations](https://github.com/jsx-eslint/eslint-plugin-react/#shareable-configs) enabled in.\ | ||
π« [Configurations](https://github.com/jsx-eslint/eslint-plugin-react/#shareable-configs) disabled in.\ | ||
π Set in the `jsx-runtime` [configuration](https://github.com/jsx-eslint/eslint-plugin-react/#shareable-configs).\ | ||
βοΈ Set in the `recommended` [configuration](https://github.com/jsx-eslint/eslint-plugin-react/#shareable-configs).\ | ||
π§ Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\ | ||
π‘ Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).\ | ||
β Deprecated. | ||
| NameΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β | Description | πΌ | π« | π§ | π‘ | β | | ||
| :----------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- | :- | :- | | ||
| [boolean-prop-naming](docs/rules/boolean-prop-naming.md) | Enforces consistent naming for boolean props | | | | | | | ||
| [button-has-type](docs/rules/button-has-type.md) | Disallow usage of `button` elements without an explicit `type` attribute | | | | | | | ||
| [default-props-match-prop-types](docs/rules/default-props-match-prop-types.md) | Enforce all defaultProps have a corresponding non-required PropType | | | | | | | ||
| [destructuring-assignment](docs/rules/destructuring-assignment.md) | Enforce consistent usage of destructuring assignment of props, state, and context | | | π§ | | | | ||
| [display-name](docs/rules/display-name.md) | Disallow missing displayName in a React component definition | βοΈ | | | | | | ||
| [forbid-component-props](docs/rules/forbid-component-props.md) | Disallow certain props on components | | | | | | | ||
| [forbid-dom-props](docs/rules/forbid-dom-props.md) | Disallow certain props on DOM Nodes | | | | | | | ||
| [forbid-elements](docs/rules/forbid-elements.md) | Disallow certain elements | | | | | | | ||
| [forbid-foreign-prop-types](docs/rules/forbid-foreign-prop-types.md) | Disallow using another component's propTypes | | | | | | | ||
| [forbid-prop-types](docs/rules/forbid-prop-types.md) | Disallow certain propTypes | | | | | | | ||
| [function-component-definition](docs/rules/function-component-definition.md) | Enforce a specific function type for function components | | | π§ | | | | ||
| [hook-use-state](docs/rules/hook-use-state.md) | Ensure destructuring and symmetric naming of useState hook value and setter variables | | | | π‘ | | | ||
| [iframe-missing-sandbox](docs/rules/iframe-missing-sandbox.md) | Enforce sandbox attribute on iframe elements | | | | | | | ||
| [jsx-boolean-value](docs/rules/jsx-boolean-value.md) | Enforce boolean attributes notation in JSX | | | π§ | | | | ||
| [jsx-child-element-spacing](docs/rules/jsx-child-element-spacing.md) | Enforce or disallow spaces inside of curly braces in JSX attributes and expressions | | | | | | | ||
| [jsx-closing-bracket-location](docs/rules/jsx-closing-bracket-location.md) | Enforce closing bracket location in JSX | | | π§ | | | | ||
| [jsx-closing-tag-location](docs/rules/jsx-closing-tag-location.md) | Enforce closing tag location for multiline JSX | | | π§ | | | | ||
| [jsx-curly-brace-presence](docs/rules/jsx-curly-brace-presence.md) | Disallow unnecessary JSX expressions when literals alone are sufficient or enforce JSX expressions on literals in JSX children or attributes | | | π§ | | | | ||
| [jsx-curly-newline](docs/rules/jsx-curly-newline.md) | Enforce consistent linebreaks in curly braces in JSX attributes and expressions | | | π§ | | | | ||
| [jsx-curly-spacing](docs/rules/jsx-curly-spacing.md) | Enforce or disallow spaces inside of curly braces in JSX attributes and expressions | | | π§ | | | | ||
| [jsx-equals-spacing](docs/rules/jsx-equals-spacing.md) | Enforce or disallow spaces around equal signs in JSX attributes | | | π§ | | | | ||
| [jsx-filename-extension](docs/rules/jsx-filename-extension.md) | Disallow file extensions that may contain JSX | | | | | | | ||
| [jsx-first-prop-new-line](docs/rules/jsx-first-prop-new-line.md) | Enforce proper position of the first property in JSX | | | π§ | | | | ||
| [jsx-fragments](docs/rules/jsx-fragments.md) | Enforce shorthand or standard form for React fragments | | | π§ | | | | ||
| [jsx-handler-names](docs/rules/jsx-handler-names.md) | Enforce event handler naming conventions in JSX | | | | | | | ||
| [jsx-indent](docs/rules/jsx-indent.md) | Enforce JSX indentation | | | π§ | | | | ||
| [jsx-indent-props](docs/rules/jsx-indent-props.md) | Enforce props indentation in JSX | | | π§ | | | | ||
| [jsx-key](docs/rules/jsx-key.md) | Disallow missing `key` props in iterators/collection literals | βοΈ | | | | | | ||
| [jsx-max-depth](docs/rules/jsx-max-depth.md) | Enforce JSX maximum depth | | | | | | | ||
| [jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md) | Enforce maximum of props on a single line in JSX | | | π§ | | | | ||
| [jsx-newline](docs/rules/jsx-newline.md) | Require or prevent a new line after jsx elements and expressions. | | | π§ | | | | ||
| [jsx-no-bind](docs/rules/jsx-no-bind.md) | Disallow `.bind()` or arrow functions in JSX props | | | | | | | ||
| [jsx-no-comment-textnodes](docs/rules/jsx-no-comment-textnodes.md) | Disallow comments from being inserted as text nodes | βοΈ | | | | | | ||
| [jsx-no-constructed-context-values](docs/rules/jsx-no-constructed-context-values.md) | Disallows JSX context provider values from taking values that will cause needless rerenders | | | | | | | ||
| [jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md) | Disallow duplicate properties in JSX | βοΈ | | | | | | ||
| [jsx-no-leaked-render](docs/rules/jsx-no-leaked-render.md) | Disallow problematic leaked values from being rendered | | | π§ | | | | ||
| [jsx-no-literals](docs/rules/jsx-no-literals.md) | Disallow usage of string literals in JSX | | | | | | | ||
| [jsx-no-script-url](docs/rules/jsx-no-script-url.md) | Disallow usage of `javascript:` URLs | | | | | | | ||
| [jsx-no-target-blank](docs/rules/jsx-no-target-blank.md) | Disallow `target="_blank"` attribute without `rel="noreferrer"` | βοΈ | | π§ | | | | ||
| [jsx-no-undef](docs/rules/jsx-no-undef.md) | Disallow undeclared variables in JSX | βοΈ | | | | | | ||
| [jsx-no-useless-fragment](docs/rules/jsx-no-useless-fragment.md) | Disallow unnecessary fragments | | | π§ | | | | ||
| [jsx-one-expression-per-line](docs/rules/jsx-one-expression-per-line.md) | Require one JSX element per line | | | π§ | | | | ||
| [jsx-pascal-case](docs/rules/jsx-pascal-case.md) | Enforce PascalCase for user-defined JSX components | | | | | | | ||
| [jsx-props-no-multi-spaces](docs/rules/jsx-props-no-multi-spaces.md) | Disallow multiple spaces between inline JSX props | | | π§ | | | | ||
| [jsx-props-no-spreading](docs/rules/jsx-props-no-spreading.md) | Disallow JSX prop spreading | | | | | | | ||
| [jsx-sort-default-props](docs/rules/jsx-sort-default-props.md) | Enforce defaultProps declarations alphabetical sorting | | | | | β | | ||
| [jsx-sort-props](docs/rules/jsx-sort-props.md) | Enforce props alphabetical sorting | | | π§ | | | | ||
| [jsx-space-before-closing](docs/rules/jsx-space-before-closing.md) | Enforce spacing before closing bracket in JSX | | | π§ | | β | | ||
| [jsx-tag-spacing](docs/rules/jsx-tag-spacing.md) | Enforce whitespace in and around the JSX opening and closing brackets | | | π§ | | | | ||
| [jsx-uses-react](docs/rules/jsx-uses-react.md) | Disallow React to be incorrectly marked as unused | βοΈ | π | | | | | ||
| [jsx-uses-vars](docs/rules/jsx-uses-vars.md) | Disallow variables used in JSX to be incorrectly marked as unused | βοΈ | | | | | | ||
| [jsx-wrap-multilines](docs/rules/jsx-wrap-multilines.md) | Disallow missing parentheses around multiline JSX | | | π§ | | | | ||
| [no-access-state-in-setstate](docs/rules/no-access-state-in-setstate.md) | Disallow when this.state is accessed within setState | | | | | | | ||
| [no-adjacent-inline-elements](docs/rules/no-adjacent-inline-elements.md) | Disallow adjacent inline elements not separated by whitespace. | | | | | | | ||
| [no-array-index-key](docs/rules/no-array-index-key.md) | Disallow usage of Array index in keys | | | | | | | ||
| [no-arrow-function-lifecycle](docs/rules/no-arrow-function-lifecycle.md) | Lifecycle methods should be methods on the prototype, not class fields | | | π§ | | | | ||
| [no-children-prop](docs/rules/no-children-prop.md) | Disallow passing of children as props | βοΈ | | | | | | ||
| [no-danger](docs/rules/no-danger.md) | Disallow usage of dangerous JSX properties | | | | | | | ||
| [no-danger-with-children](docs/rules/no-danger-with-children.md) | Disallow when a DOM element is using both children and dangerouslySetInnerHTML | βοΈ | | | | | | ||
| [no-deprecated](docs/rules/no-deprecated.md) | Disallow usage of deprecated methods | βοΈ | | | | | | ||
| [no-did-mount-set-state](docs/rules/no-did-mount-set-state.md) | Disallow usage of setState in componentDidMount | | | | | | | ||
| [no-did-update-set-state](docs/rules/no-did-update-set-state.md) | Disallow usage of setState in componentDidUpdate | | | | | | | ||
| [no-direct-mutation-state](docs/rules/no-direct-mutation-state.md) | Disallow direct mutation of this.state | βοΈ | | | | | | ||
| [no-find-dom-node](docs/rules/no-find-dom-node.md) | Disallow usage of findDOMNode | βοΈ | | | | | | ||
| [no-invalid-html-attribute](docs/rules/no-invalid-html-attribute.md) | Disallow usage of invalid attributes | | | | π‘ | | | ||
| [no-is-mounted](docs/rules/no-is-mounted.md) | Disallow usage of isMounted | βοΈ | | | | | | ||
| [no-multi-comp](docs/rules/no-multi-comp.md) | Disallow multiple component definition per file | | | | | | | ||
| [no-namespace](docs/rules/no-namespace.md) | Enforce that namespaces are not used in React elements | | | | | | | ||
| [no-object-type-as-default-prop](docs/rules/no-object-type-as-default-prop.md) | Disallow usage of referential-type variables as default param in functional component | | | | | | | ||
| [no-redundant-should-component-update](docs/rules/no-redundant-should-component-update.md) | Disallow usage of shouldComponentUpdate when extending React.PureComponent | | | | | | | ||
| [no-render-return-value](docs/rules/no-render-return-value.md) | Disallow usage of the return value of ReactDOM.render | βοΈ | | | | | | ||
| [no-set-state](docs/rules/no-set-state.md) | Disallow usage of setState | | | | | | | ||
| [no-string-refs](docs/rules/no-string-refs.md) | Disallow using string references | βοΈ | | | | | | ||
| [no-this-in-sfc](docs/rules/no-this-in-sfc.md) | Disallow `this` from being used in stateless functional components | | | | | | | ||
| [no-typos](docs/rules/no-typos.md) | Disallow common typos | | | | | | | ||
| [no-unescaped-entities](docs/rules/no-unescaped-entities.md) | Disallow unescaped HTML entities from appearing in markup | βοΈ | | | | | | ||
| [no-unknown-property](docs/rules/no-unknown-property.md) | Disallow usage of unknown DOM property | βοΈ | | π§ | | | | ||
| [no-unsafe](docs/rules/no-unsafe.md) | Disallow usage of unsafe lifecycle methods | | βοΈ | | | | | ||
| [no-unstable-nested-components](docs/rules/no-unstable-nested-components.md) | Disallow creating unstable components inside components | | | | | | | ||
| [no-unused-class-component-methods](docs/rules/no-unused-class-component-methods.md) | Disallow declaring unused methods of component class | | | | | | | ||
| [no-unused-prop-types](docs/rules/no-unused-prop-types.md) | Disallow definitions of unused propTypes | | | | | | | ||
| [no-unused-state](docs/rules/no-unused-state.md) | Disallow definitions of unused state | | | | | | | ||
| [no-will-update-set-state](docs/rules/no-will-update-set-state.md) | Disallow usage of setState in componentWillUpdate | | | | | | | ||
| [prefer-es6-class](docs/rules/prefer-es6-class.md) | Enforce ES5 or ES6 class for React Components | | | | | | | ||
| [prefer-exact-props](docs/rules/prefer-exact-props.md) | Prefer exact proptype definitions | | | | | | | ||
| [prefer-read-only-props](docs/rules/prefer-read-only-props.md) | Enforce that props are read-only | | | π§ | | | | ||
| [prefer-stateless-function](docs/rules/prefer-stateless-function.md) | Enforce stateless components to be written as a pure function | | | | | | | ||
| [prop-types](docs/rules/prop-types.md) | Disallow missing props validation in a React component definition | βοΈ | | | | | | ||
| [react-in-jsx-scope](docs/rules/react-in-jsx-scope.md) | Disallow missing React when using JSX | βοΈ | π | | | | | ||
| [require-default-props](docs/rules/require-default-props.md) | Enforce a defaultProps definition for every prop that is not a required prop | | | | | | | ||
| [require-optimization](docs/rules/require-optimization.md) | Enforce React components to have a shouldComponentUpdate method | | | | | | | ||
| [require-render-return](docs/rules/require-render-return.md) | Enforce ES5 or ES6 class for returning value in render function | βοΈ | | | | | | ||
| [self-closing-comp](docs/rules/self-closing-comp.md) | Disallow extra closing tags for components without children | | | π§ | | | | ||
| [sort-comp](docs/rules/sort-comp.md) | Enforce component methods order | | | | | | | ||
| [sort-default-props](docs/rules/sort-default-props.md) | Enforce defaultProps declarations alphabetical sorting | | | | | | | ||
| [sort-prop-types](docs/rules/sort-prop-types.md) | Enforce propTypes declarations alphabetical sorting | | | π§ | | | | ||
| [state-in-constructor](docs/rules/state-in-constructor.md) | Enforce class component state initialization style | | | | | | | ||
| [static-property-placement](docs/rules/static-property-placement.md) | Enforces where React component static properties should be positioned. | | | | | | | ||
| [style-prop-object](docs/rules/style-prop-object.md) | Enforce style prop value is an object | | | | | | | ||
| [void-dom-elements-no-children](docs/rules/void-dom-elements-no-children.md) | Disallow void DOM elements (e.g. `<img />`, `<br />`) from receiving children | | | | | | | ||
<!-- end auto-generated rules list --> | ||
## Other useful plugins | ||
- Rules of Hooks: [eslint-plugin-react-hooks](https://github.com/facebook/react/tree/master/packages/eslint-plugin-react-hooks) | ||
- JSX accessibility: [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y) | ||
- React Native: [eslint-plugin-react-native](https://github.com/Intellicode/eslint-plugin-react-native) | ||
## License | ||
@@ -271,0 +408,0 @@ |
789871
135
21260
421
16
+ Addedarray.prototype.tosorted@1.1.4(transitive)
Updatedarray-includes@^3.1.6
Updatedobject.entries@^1.1.6
Updatedobject.fromentries@^2.0.6
Updatedobject.hasown@^1.1.2
Updatedobject.values@^1.1.6
Updatedresolve@^2.0.0-next.4