eslint-plugin-react
Advanced tools
Comparing version 7.31.10 to 7.33.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 @@ }; |
@@ -137,3 +137,3 @@ /** | ||
&& !isAssignmentLHS(node); | ||
if (isPropUsed && configuration === 'always') { | ||
if (isPropUsed && configuration === 'always' && !node.optional) { | ||
report(context, messages.useDestructAssignment, 'useDestructAssignment', { | ||
@@ -140,0 +140,0 @@ node, |
@@ -9,4 +9,7 @@ /** | ||
const values = require('object.values'); | ||
const filter = require('es-iterator-helpers/Iterator.prototype.filter'); | ||
const forEach = require('es-iterator-helpers/Iterator.prototype.forEach'); | ||
const Components = require('../util/Components'); | ||
const isCreateContext = require('../util/isCreateContext'); | ||
const astUtil = require('../util/ast'); | ||
@@ -25,2 +28,3 @@ const componentUtil = require('../util/componentUtil'); | ||
noDisplayName: 'Component definition is missing display name', | ||
noContextDisplayName: 'Context definition is missing display name', | ||
}; | ||
@@ -45,2 +49,5 @@ | ||
}, | ||
checkContextObjects: { | ||
type: 'boolean', | ||
}, | ||
}, | ||
@@ -54,3 +61,6 @@ additionalProperties: false, | ||
const ignoreTranspilerName = config.ignoreTranspilerName || false; | ||
const checkContextObjects = (config.checkContextObjects || false) && testReactVersion(context, '>= 16.3.0'); | ||
const contextObjects = new Map(); | ||
/** | ||
@@ -95,2 +105,12 @@ * Mark a prop type as declared | ||
/** | ||
* Reports missing display name for a given context object | ||
* @param {Object} contextObj The context object to process | ||
*/ | ||
function reportMissingContextDisplayName(contextObj) { | ||
report(context, messages.noContextDisplayName, 'noContextDisplayName', { | ||
node: contextObj.node, | ||
}); | ||
} | ||
/** | ||
* Checks if the component have a name set by the transpiler | ||
@@ -152,2 +172,12 @@ * @param {ASTNode} node The AST node being checked. | ||
return { | ||
ExpressionStatement(node) { | ||
if (checkContextObjects && isCreateContext(node)) { | ||
contextObjects.set(node.expression.left.name, { node, hasDisplayName: false }); | ||
} | ||
}, | ||
VariableDeclarator(node) { | ||
if (checkContextObjects && isCreateContext(node)) { | ||
contextObjects.set(node.id.name, { node, hasDisplayName: false }); | ||
} | ||
}, | ||
'ClassProperty, PropertyDefinition'(node) { | ||
@@ -164,2 +194,10 @@ if (!propsUtil.isDisplayNameDeclaration(node)) { | ||
} | ||
if ( | ||
checkContextObjects | ||
&& node.object | ||
&& node.object.name | ||
&& contextObjects.has(node.object.name) | ||
) { | ||
contextObjects.get(node.object.name).hasDisplayName = true; | ||
} | ||
const component = utils.getRelatedComponent(node); | ||
@@ -172,3 +210,3 @@ if (!component) { | ||
FunctionExpression(node) { | ||
'FunctionExpression, FunctionDeclaration, ArrowFunctionExpression'(node) { | ||
if (ignoreTranspilerName || !hasTranspilerName(node)) { | ||
@@ -182,21 +220,2 @@ return; | ||
FunctionDeclaration(node) { | ||
if (ignoreTranspilerName || !hasTranspilerName(node)) { | ||
return; | ||
} | ||
if (components.get(node)) { | ||
markDisplayNameAsDeclared(node); | ||
} | ||
}, | ||
ArrowFunctionExpression(node) { | ||
if (ignoreTranspilerName || !hasTranspilerName(node)) { | ||
return; | ||
} | ||
if (components.get(node)) { | ||
markDisplayNameAsDeclared(node); | ||
} | ||
}, | ||
MethodDefinition(node) { | ||
@@ -209,3 +228,3 @@ if (!propsUtil.isDisplayNameDeclaration(node.key)) { | ||
ClassExpression(node) { | ||
'ClassExpression, ClassDeclaration'(node) { | ||
if (ignoreTranspilerName || !hasTranspilerName(node)) { | ||
@@ -217,9 +236,2 @@ return; | ||
ClassDeclaration(node) { | ||
if (ignoreTranspilerName || !hasTranspilerName(node)) { | ||
return; | ||
} | ||
markDisplayNameAsDeclared(node); | ||
}, | ||
ObjectExpression(node) { | ||
@@ -272,2 +284,9 @@ if (!componentUtil.isES5Component(node, context)) { | ||
}); | ||
if (checkContextObjects) { | ||
// Report missing display name for all context objects | ||
forEach( | ||
filter(contextObjects.values(), (v) => !v.hasDisplayName), | ||
(contextObj) => reportMissingContextDisplayName(contextObj) | ||
); | ||
} | ||
}, | ||
@@ -274,0 +293,0 @@ }; |
@@ -42,22 +42,33 @@ /** | ||
items: { | ||
oneOf: [{ | ||
type: 'string', | ||
}, { | ||
type: 'object', | ||
properties: { | ||
propName: { | ||
type: 'string', | ||
anyOf: [ | ||
{ type: 'string' }, | ||
{ | ||
type: 'object', | ||
properties: { | ||
propName: { type: 'string' }, | ||
allowedFor: { | ||
type: 'array', | ||
uniqueItems: true, | ||
items: { type: 'string' }, | ||
}, | ||
message: { type: 'string' }, | ||
}, | ||
allowedFor: { | ||
type: 'array', | ||
uniqueItems: true, | ||
items: { | ||
type: 'string', | ||
additionalProperties: false, | ||
}, | ||
{ | ||
type: 'object', | ||
properties: { | ||
propName: { type: 'string' }, | ||
disallowedFor: { | ||
type: 'array', | ||
uniqueItems: true, | ||
minItems: 1, | ||
items: { type: 'string' }, | ||
}, | ||
message: { type: 'string' }, | ||
}, | ||
message: { | ||
type: 'string', | ||
}, | ||
required: ['disallowedFor'], | ||
additionalProperties: false, | ||
}, | ||
}], | ||
], | ||
}, | ||
@@ -75,2 +86,3 @@ }, | ||
allowList: typeof value === 'string' ? [] : (value.allowedFor || []), | ||
disallowList: typeof value === 'string' ? [] : (value.disallowedFor || []), | ||
message: typeof value === 'string' ? null : value.message, | ||
@@ -83,5 +95,13 @@ }; | ||
const options = forbid.get(prop); | ||
const allowList = options ? options.allowList : undefined; | ||
if (!options) { | ||
return false; | ||
} | ||
// disallowList should have a least one item (schema configuration) | ||
const isTagForbidden = options.disallowList.length > 0 | ||
? options.disallowList.indexOf(tagName) !== -1 | ||
: options.allowList.indexOf(tagName) === -1; | ||
// if the tagName is undefined (`<this.something>`), we assume it's a forbidden element | ||
return typeof allowList !== 'undefined' && (typeof tagName === 'undefined' || allowList.indexOf(tagName) === -1); | ||
return typeof tagName === 'undefined' || isTagForbidden; | ||
} | ||
@@ -88,0 +108,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: [ | ||
{ | ||
@@ -188,3 +188,3 @@ type: 'object', | ||
? expression.quasis[0].value.raw | ||
: expression.raw.substring(1, expression.raw.length - 1) | ||
: expression.raw.slice(1, -1) | ||
}"`; | ||
@@ -227,3 +227,3 @@ } else if (jsxUtil.isJSX(expression)) { | ||
? `{"${escapeDoubleQuotes(escapeBackslashes( | ||
literalNode.raw.substring(1, literalNode.raw.length - 1) | ||
literalNode.raw.slice(1, -1) | ||
))}"}` | ||
@@ -230,0 +230,0 @@ : wrapWithCurlyBraces(literalNode.raw); |
@@ -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', |
@@ -33,3 +33,3 @@ /** | ||
schema: [{ | ||
enum: ['always', 'never', 'multiline', 'multiline-multiprop'], | ||
enum: ['always', 'never', 'multiline', 'multiline-multiprop', 'multiprop'], | ||
}], | ||
@@ -50,2 +50,3 @@ }, | ||
|| (configuration === 'multiline-multiprop' && isMultilineJSX(node) && node.attributes.length > 1) | ||
|| (configuration === 'multiprop' && node.attributes.length > 1) | ||
|| (configuration === 'always') | ||
@@ -58,3 +59,3 @@ ) { | ||
fix(fixer) { | ||
return fixer.replaceTextRange([node.name.range[1], decl.range[0]], '\n'); | ||
return fixer.replaceTextRange([(node.typeParameters || node.name).range[1], decl.range[0]], '\n'); | ||
}, | ||
@@ -65,3 +66,6 @@ }); | ||
}); | ||
} else if (configuration === 'never' && node.attributes.length > 0) { | ||
} else if ( | ||
(configuration === 'never' && node.attributes.length > 0) | ||
|| (configuration === 'multiprop' && isMultilineJSX(node) && node.attributes.length <= 1) | ||
) { | ||
const firstNode = node.attributes[0]; | ||
@@ -68,0 +72,0 @@ if (node.loc.start.line < firstNode.loc.start.line) { |
@@ -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 @@ |
@@ -8,4 +8,8 @@ /** | ||
const find = require('es-iterator-helpers/Iterator.prototype.find'); | ||
const from = require('es-iterator-helpers/Iterator.from'); | ||
const docsUrl = require('../util/docsUrl'); | ||
const report = require('../util/report'); | ||
const testReactVersion = require('../util/version').testReactVersion; | ||
const isParenthesized = require('../util/ast').isParenthesized; | ||
@@ -64,5 +68,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}`); | ||
@@ -121,3 +143,3 @@ } | ||
const validStrategies = new Set(config.validStrategies || DEFAULT_VALID_STRATEGIES); | ||
const fixStrategy = Array.from(validStrategies)[0]; | ||
const fixStrategy = find(from(validStrategies), () => true); | ||
@@ -136,2 +158,5 @@ return { | ||
if (testReactVersion(context, '>= 18') && leftSide.type === 'Literal' && leftSide.value === '') { | ||
return; | ||
} | ||
report(context, messages.noPotentialLeakedRender, 'noPotentialLeakedRender', { | ||
@@ -138,0 +163,0 @@ node, |
@@ -9,2 +9,5 @@ /** | ||
const iterFrom = require('es-iterator-helpers/Iterator.from'); | ||
const map = require('es-iterator-helpers/Iterator.prototype.map'); | ||
const docsUrl = require('../util/docsUrl'); | ||
@@ -71,3 +74,3 @@ const report = require('../util/report'); | ||
const config = Object.assign({}, defaults, context.options[0] || {}); | ||
config.allowedStrings = new Set(config.allowedStrings.map(trimIfString)); | ||
config.allowedStrings = new Set(map(iterFrom(config.allowedStrings), trimIfString)); | ||
@@ -74,0 +77,0 @@ function defaultMessageId() { |
@@ -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))); | ||
@@ -253,3 +255,3 @@ return function fixFunction(fixer) { | ||
const sortedAttr = sortedAttributeGroups[ii][jj]; | ||
const sortedAttrText = source.substring(sortedAttr.range[0], attributeMap.get(sortedAttr).end); | ||
const sortedAttrText = source.slice(sortedAttr.range[0], attributeMap.get(sortedAttr).end); | ||
fixers.push({ | ||
@@ -270,6 +272,6 @@ range: [attr.range[0], attributeMap.get(attr).end], | ||
fixers.forEach((fix) => { | ||
source = `${source.substr(0, fix.range[0])}${fix.text}${source.substr(fix.range[1])}`; | ||
source = `${source.slice(0, fix.range[0])}${fix.text}${source.slice(fix.range[1])}`; | ||
}); | ||
return fixer.replaceTextRange([rangeStart, rangeEnd], source.substr(rangeStart, rangeEnd - rangeStart)); | ||
return fixer.replaceTextRange([rangeStart, rangeEnd], source.slice(rangeStart, rangeEnd)); | ||
}; | ||
@@ -276,0 +278,0 @@ } |
@@ -28,2 +28,3 @@ /** | ||
deprecated: true, | ||
replacedBy: ['jsx-tag-spacing'], | ||
docs: { | ||
@@ -30,0 +31,0 @@ description: 'Enforce spacing before closing bracket in JSX', |
@@ -68,2 +68,3 @@ /** | ||
findIndex: 1, | ||
flatMap: 1, | ||
forEach: 1, | ||
@@ -70,0 +71,0 @@ map: 1, |
@@ -10,3 +10,3 @@ /** | ||
const values = require('object.values'); | ||
const entries = require('object.entries'); | ||
const astUtil = require('../util/ast'); | ||
@@ -26,2 +26,4 @@ const componentUtil = require('../util/componentUtil'); | ||
'react-addons-perf': ['ReactPerf', 'Perf'], | ||
'react-dom': ['ReactDOM'], | ||
'react-dom/server': ['ReactDOMServer'], | ||
}; | ||
@@ -87,2 +89,25 @@ | ||
]; | ||
// 18.0.0 | ||
// https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#deprecations | ||
deprecated['ReactDOM.render'] = [ | ||
'18.0.0', | ||
'createRoot', | ||
'https://reactjs.org/link/switch-to-createroot', | ||
]; | ||
deprecated['ReactDOM.hydrate'] = [ | ||
'18.0.0', | ||
'hydrateRoot', | ||
'https://reactjs.org/link/switch-to-createroot', | ||
]; | ||
deprecated['ReactDOM.unmountComponentAtNode'] = [ | ||
'18.0.0', | ||
'root.unmount', | ||
'https://reactjs.org/link/switch-to-createroot', | ||
]; | ||
deprecated['ReactDOMServer.renderToNodeStream'] = [ | ||
'18.0.0', | ||
'renderToPipeableStream', | ||
'https://reactjs.org/docs/react-dom-server.html#rendertonodestream', | ||
]; | ||
return deprecated; | ||
@@ -143,7 +168,18 @@ } | ||
if (!node.init) { | ||
return moduleName; | ||
return false; | ||
} | ||
values(MODULES).some((moduleNames) => { | ||
moduleName = moduleNames.find((name) => name === node.init.name); | ||
entries(MODULES).some((entry) => { | ||
const key = entry[0]; | ||
const moduleNames = entry[1]; | ||
if ( | ||
node.init.arguments | ||
&& node.init.arguments.length > 0 | ||
&& node.init.arguments[0] | ||
&& key === node.init.arguments[0].value | ||
) { | ||
moduleName = MODULES[key][0]; | ||
} else { | ||
moduleName = moduleNames.find((name) => name === node.init.name); | ||
} | ||
return moduleName; | ||
@@ -196,7 +232,4 @@ }); | ||
} | ||
node.specifiers.forEach((specifier) => { | ||
if (!specifier.imported) { | ||
return; | ||
} | ||
checkDeprecation(node, `${MODULES[node.source.value][0]}.${specifier.imported.name}`); | ||
node.specifiers.filter(((s) => s.imported)).forEach((specifier) => { | ||
checkDeprecation(node, `${MODULES[node.source.value][0]}.${specifier.imported.name}`, specifier); | ||
}); | ||
@@ -220,6 +253,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}`, property); | ||
}); | ||
@@ -226,0 +257,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'); | ||
@@ -222,4 +223,5 @@ // ------------------------------------------------------------------------------ | ||
*/ | ||
const COMPONENT_ATTRIBUTE_MAP = new Map(); | ||
COMPONENT_ATTRIBUTE_MAP.set('rel', new Set(['link', 'a', 'area', 'form'])); | ||
const COMPONENT_ATTRIBUTE_MAP = new Map([ | ||
['rel', new Set(['link', 'a', 'area', 'form'])], | ||
]); | ||
@@ -237,2 +239,7 @@ const messages = { | ||
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}}β', | ||
}; | ||
@@ -260,5 +267,8 @@ | ||
data: { attributeName }, | ||
fix(fixer) { | ||
return fixer.remove(parentNode); | ||
}, | ||
suggest: [ | ||
Object.assign( | ||
getMessageData('suggestRemoveNonString', messages.suggestRemoveNonString), | ||
{ fix(fixer) { return fixer.remove(parentNode); } } | ||
), | ||
], | ||
}); | ||
@@ -272,5 +282,8 @@ return; | ||
data: { attributeName }, | ||
fix(fixer) { | ||
return fixer.remove(parentNode); | ||
}, | ||
suggest: [ | ||
Object.assign( | ||
getMessageData('suggestRemoveEmpty', messages.suggestRemoveEmpty), | ||
{ fix(fixer) { return fixer.remove(node.parent); } } | ||
), | ||
], | ||
}); | ||
@@ -284,12 +297,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, | ||
}); | ||
@@ -304,5 +324,3 @@ } else if (!allowedTags.has(parentNodeName)) { | ||
}, | ||
fix(fixer) { | ||
return fixer.removeRange(singlePart.range); | ||
}, | ||
suggest, | ||
}); | ||
@@ -334,2 +352,3 @@ } | ||
}, | ||
suggest: false, | ||
}); | ||
@@ -348,5 +367,8 @@ } | ||
data: { attributeName }, | ||
fix(fixer) { | ||
return fixer.removeRange(whitespacePart.range); | ||
}, | ||
suggest: [ | ||
Object.assign( | ||
getMessageData('suggestRemoveWhitespaces', messages.suggestRemoveWhitespaces), | ||
{ fix(fixer) { return fixer.removeRange(whitespacePart.range); } } | ||
), | ||
], | ||
}); | ||
@@ -357,5 +379,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'); } } | ||
), | ||
], | ||
}); | ||
@@ -371,6 +396,2 @@ } | ||
function fix(fixer) { | ||
return fixer.remove(node); | ||
} | ||
const parentNodeName = node.parent.name.name; | ||
@@ -383,3 +404,3 @@ if (!COMPONENT_ATTRIBUTE_MAP.has(attribute) || !COMPONENT_ATTRIBUTE_MAP.get(attribute).has(parentNodeName)) { | ||
report(context, messages.onlyMeaningfulFor, 'onlyMeaningfulFor', { | ||
node, | ||
node: node.name, | ||
data: { | ||
@@ -389,3 +410,8 @@ attributeName: attribute, | ||
}, | ||
fix, | ||
suggest: [ | ||
Object.assign( | ||
getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault), | ||
{ fix(fixer) { return fixer.remove(node); } } | ||
), | ||
], | ||
}); | ||
@@ -395,7 +421,14 @@ return; | ||
function fix(fixer) { return fixer.remove(node); } | ||
if (!node.value) { | ||
report(context, messages.emptyIsMeaningless, 'emptyIsMeaningless', { | ||
node, | ||
node: node.name, | ||
data: { attributeName: attribute }, | ||
fix, | ||
suggest: [ | ||
Object.assign( | ||
getMessageData('suggestRemoveEmpty', messages.suggestRemoveEmpty), | ||
{ fix } | ||
), | ||
], | ||
}); | ||
@@ -419,14 +452,21 @@ return; | ||
report(context, messages.onlyStrings, 'onlyStrings', { | ||
node, | ||
node: node.value, | ||
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, | ||
node: node.value, | ||
data: { attributeName: attribute }, | ||
fix, | ||
suggest: [ | ||
Object.assign( | ||
getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault), | ||
{ fix } | ||
), | ||
], | ||
}); | ||
@@ -459,7 +499,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', { | ||
@@ -472,2 +515,3 @@ node: value, | ||
}, | ||
suggest: false, | ||
}); | ||
@@ -508,3 +552,3 @@ } | ||
report(context, messages.onlyMeaningfulFor, 'onlyMeaningfulFor', { | ||
node, | ||
node: prop.key, | ||
data: { | ||
@@ -514,2 +558,3 @@ attributeName: attribute, | ||
}, | ||
suggest: false, | ||
}); | ||
@@ -527,2 +572,3 @@ | ||
}, | ||
suggest: false, | ||
}); | ||
@@ -554,3 +600,2 @@ | ||
meta: { | ||
fixable: 'code', | ||
docs: { | ||
@@ -569,2 +614,4 @@ description: 'Disallow usage of invalid attributes', | ||
}], | ||
type: 'suggestion', | ||
hasSuggestions: true, // eslint-disable-line eslint-plugin/require-meta-has-suggestions | ||
}, | ||
@@ -571,0 +618,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', | ||
@@ -97,3 +97,3 @@ }, { | ||
} | ||
rawLine = rawLine.substring(start, end); | ||
rawLine = rawLine.slice(start, end); | ||
for (let j = 0; j < entities.length; j++) { | ||
@@ -100,0 +100,0 @@ for (let index = 0; index < rawLine.length; index++) { |
@@ -45,2 +45,3 @@ /** | ||
'line', | ||
'marker', | ||
'mask', | ||
@@ -52,2 +53,3 @@ 'path', | ||
'svg', | ||
'symbol', | ||
'text', | ||
@@ -85,3 +87,3 @@ 'textPath', | ||
onError: ['audio', 'video', 'img', 'link', 'source', 'script', 'picture', 'iframe'], | ||
onLoad: ['script', 'img', 'link', 'picture', 'iframe', 'object'], | ||
onLoad: ['script', 'img', 'link', 'picture', 'iframe', 'object', 'source'], | ||
onLoadedData: ['audio', 'video'], | ||
@@ -208,3 +210,3 @@ onLoadedMetadata: ['audio', 'video'], | ||
// 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 | ||
@@ -551,3 +553,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 | ||
@@ -554,0 +556,0 @@ if (!isValidHTMLTagInJSX(node)) { return; } |
@@ -111,4 +111,7 @@ /** | ||
const propertyNode = astUtil.getComponentProperties(node) | ||
.find((property) => astUtil.getPropertyName(property) === method); | ||
report(context, messages.unsafeMethod, 'unsafeMethod', { | ||
node, | ||
node: propertyNode, | ||
data: { | ||
@@ -139,3 +142,5 @@ method, | ||
const methods = getLifeCycleMethods(node); | ||
methods.forEach((method) => checkUnsafe(node, method)); | ||
methods | ||
.sort((a, b) => a.localeCompare(b)) | ||
.forEach((method) => checkUnsafe(node, method)); | ||
} | ||
@@ -142,0 +147,0 @@ } |
@@ -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 @@ }; |
@@ -382,10 +382,12 @@ /** | ||
const stateRefs = argVar.references; | ||
if (argVar) { | ||
const stateRefs = argVar.references; | ||
stateRefs.forEach((ref) => { | ||
const identifier = ref.identifier; | ||
if (identifier && identifier.parent && identifier.parent.type === 'MemberExpression') { | ||
addUsedStateField(identifier.parent.property); | ||
} | ||
}); | ||
stateRefs.forEach((ref) => { | ||
const identifier = ref.identifier; | ||
if (identifier && identifier.parent && identifier.parent.type === 'MemberExpression') { | ||
addUsedStateField(identifier.parent.property); | ||
} | ||
}); | ||
} | ||
}, | ||
@@ -392,0 +394,0 @@ |
@@ -8,2 +8,5 @@ /** | ||
const flatMap = require('array.prototype.flatmap'); | ||
const values = require('object.values'); | ||
const Components = require('../util/Components'); | ||
@@ -17,2 +20,6 @@ const docsUrl = require('../util/docsUrl'); | ||
function isTypescriptPropertyType(node) { | ||
return node.type === 'TSPropertySignature'; | ||
} | ||
function isCovariant(node) { | ||
@@ -29,2 +36,10 @@ return (node.variance && node.variance.kind === 'plus') | ||
function isReadonly(node) { | ||
return ( | ||
node.typeAnnotation | ||
&& node.typeAnnotation.parent | ||
&& node.typeAnnotation.parent.readonly | ||
); | ||
} | ||
// ------------------------------------------------------------------------------ | ||
@@ -53,41 +68,53 @@ // Rule Definition | ||
create: Components.detect((context, components) => ({ | ||
'Program:exit'() { | ||
const list = components.list(); | ||
create: Components.detect((context, components) => { | ||
function reportReadOnlyProp(prop, propName, fixer) { | ||
report(context, messages.readOnlyProp, 'readOnlyProp', { | ||
node: prop.node, | ||
data: { | ||
name: propName, | ||
}, | ||
fix: fixer, | ||
}); | ||
} | ||
Object.keys(list).forEach((key) => { | ||
const component = list[key]; | ||
return { | ||
'Program:exit'() { | ||
flatMap( | ||
values(components.list()), | ||
(component) => component.declaredPropTypes || [] | ||
).forEach((declaredPropTypes) => { | ||
Object.keys(declaredPropTypes).forEach((propName) => { | ||
const prop = declaredPropTypes[propName]; | ||
if (!prop.node) { | ||
return; | ||
} | ||
if (!component.declaredPropTypes) { | ||
return; | ||
} | ||
if (isFlowPropertyType(prop.node)) { | ||
if (!isCovariant(prop.node)) { | ||
reportReadOnlyProp(prop, propName, (fixer) => { | ||
if (!prop.node.variance) { | ||
// Insert covariance | ||
return fixer.insertTextBefore(prop.node, '+'); | ||
} | ||
Object.keys(component.declaredPropTypes).forEach((propName) => { | ||
const prop = component.declaredPropTypes[propName]; | ||
// Replace contravariance with covariance | ||
return fixer.replaceText(prop.node.variance, '+'); | ||
}); | ||
} | ||
if (!prop.node || !isFlowPropertyType(prop.node)) { | ||
return; | ||
} | ||
return; | ||
} | ||
if (!isCovariant(prop.node)) { | ||
report(context, messages.readOnlyProp, 'readOnlyProp', { | ||
node: prop.node, | ||
data: { | ||
name: propName, | ||
}, | ||
fix: (fixer) => { | ||
if (!prop.node.variance) { | ||
// Insert covariance | ||
return fixer.insertTextBefore(prop.node, '+'); | ||
} | ||
// Replace contravariance with covariance | ||
return fixer.replaceText(prop.node.variance, '+'); | ||
}, | ||
}); | ||
} | ||
if (isTypescriptPropertyType(prop.node)) { | ||
if (!isReadonly(prop.node)) { | ||
reportReadOnlyProp(prop, propName, (fixer) => ( | ||
fixer.insertTextBefore(prop.node, 'readonly ') | ||
)); | ||
} | ||
} | ||
}); | ||
}); | ||
}); | ||
}, | ||
})), | ||
}, | ||
}; | ||
}), | ||
}; |
@@ -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 @@ }; |
@@ -44,10 +44,6 @@ /** | ||
classes: { | ||
allow: { | ||
enum: ['defaultProps', 'ignore'], | ||
}, | ||
enum: ['defaultProps', 'ignore'], | ||
}, | ||
functions: { | ||
allow: { | ||
enum: ['defaultArguments', 'defaultProps', 'ignore'], | ||
}, | ||
enum: ['defaultArguments', 'defaultProps', 'ignore'], | ||
}, | ||
@@ -54,0 +50,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,14 @@ 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, | ||
noSortAlphabetically, | ||
sortShapeProp | ||
); | ||
} | ||
@@ -156,3 +157,3 @@ const callbackPropsLastSeen = new WeakSet(); | ||
node: curr, | ||
// fix | ||
fix, | ||
}); | ||
@@ -175,3 +176,3 @@ } | ||
node: prev, | ||
// fix | ||
fix, | ||
}); | ||
@@ -188,3 +189,3 @@ } | ||
node: curr, | ||
// fix | ||
fix, | ||
}); | ||
@@ -269,5 +270,4 @@ } | ||
}, | ||
}; | ||
}, | ||
}; |
@@ -11,2 +11,4 @@ /** | ||
const values = require('object.values'); | ||
const iterFrom = require('es-iterator-helpers/Iterator.from'); | ||
const map = require('es-iterator-helpers/Iterator.prototype.map'); | ||
@@ -113,3 +115,3 @@ const variableUtil = require('./variable'); | ||
let component = list[getId(node)]; | ||
while (!component) { | ||
while (!component || component.confidence < 1) { | ||
node = node.parent; | ||
@@ -186,3 +188,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; | ||
} | ||
@@ -273,13 +275,11 @@ | ||
/** @type {{[key: string]: Function}} */ | ||
const rule = {}; | ||
handlersByKey.forEach((fns, key) => { | ||
rule[key] = function mergedHandler(node) { | ||
fns.forEach((fn) => { | ||
/** @type {{ [key: string]: Function }} */ | ||
return fromEntries(map(iterFrom(handlersByKey), (entry) => [ | ||
entry[0], | ||
function mergedHandler(node) { | ||
entry[1].forEach((fn) => { | ||
fn(node); | ||
}); | ||
}; | ||
}); | ||
return rule; | ||
}, | ||
])); | ||
} | ||
@@ -484,3 +484,2 @@ | ||
if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') { | ||
const isMethod = parent.type === 'Property' && parent.method; | ||
const isPropertyAssignment = parent.type === 'AssignmentExpression' | ||
@@ -570,2 +569,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(...)` | ||
@@ -585,6 +596,2 @@ const pragmaComponentWrapper = utils.getPragmaComponentWrapper(node); | ||
if (isMethod && !isFirstLetterCapitalized(node.parent.key.name)) { | ||
return utils.isReturningJSX(node) ? node : undefined; | ||
} | ||
if (node.id) { | ||
@@ -863,9 +870,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; | ||
@@ -882,3 +884,3 @@ } | ||
node = utils.getParentComponent(); | ||
node = utils.getStatelessComponent(node); | ||
if (!node) { | ||
@@ -896,9 +898,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; | ||
@@ -905,0 +902,0 @@ } |
@@ -9,6 +9,6 @@ 'use strict'; | ||
function isFirstLetterCapitalized(word) { | ||
if (!word || word.charAt(0) === '_') { | ||
if (!word) { | ||
return false; | ||
} | ||
const firstLetter = word.charAt(0); | ||
const firstLetter = word.replace(/^_+/, '').charAt(0); | ||
return firstLetter.toUpperCase() === firstLetter; | ||
@@ -15,0 +15,0 @@ } |
@@ -7,2 +7,5 @@ /** | ||
const iterFrom = require('es-iterator-helpers/Iterator.from'); | ||
const map = require('es-iterator-helpers/Iterator.prototype.map'); | ||
/** TODO: type {(string | { name: string, linkAttribute: string })[]} */ | ||
@@ -23,3 +26,3 @@ /** @type {any} */ | ||
); | ||
return new Map(formComponents.map((value) => { | ||
return new Map(map(iterFrom(formComponents), (value) => { | ||
if (typeof value === 'string') { | ||
@@ -37,3 +40,3 @@ return [value, DEFAULT_FORM_ATTRIBUTE]; | ||
); | ||
return new Map(linkComponents.map((value) => { | ||
return new Map(map(iterFrom(linkComponents), (value) => { | ||
if (typeof value === 'string') { | ||
@@ -40,0 +43,0 @@ return [value, DEFAULT_LINK_ATTRIBUTE]; |
@@ -7,2 +7,4 @@ /** | ||
const toSorted = require('array.prototype.tosorted'); | ||
const astUtil = require('./ast'); | ||
@@ -71,5 +73,6 @@ | ||
* @param {Boolean=} callbacksLast whether or not to sort callbacks after everything else. | ||
* @param {Boolean=} noSortAlphabetically whether or not to disable alphabetical sorting of the elements. | ||
* @returns {Number} the sort order of the two elements. | ||
*/ | ||
function sorter(a, b, context, ignoreCase, requiredFirst, callbacksLast) { | ||
function sorter(a, b, context, ignoreCase, requiredFirst, callbacksLast, noSortAlphabetically) { | ||
const aKey = String(astUtil.getKeyValue(context, a)); | ||
@@ -96,15 +99,19 @@ const bKey = String(astUtil.getKeyValue(context, b)); | ||
if (ignoreCase) { | ||
return aKey.localeCompare(bKey); | ||
} | ||
if (!noSortAlphabetically) { | ||
if (ignoreCase) { | ||
return aKey.localeCompare(bKey); | ||
} | ||
if (aKey < bKey) { | ||
return -1; | ||
if (aKey < bKey) { | ||
return -1; | ||
} | ||
if (aKey > bKey) { | ||
return 1; | ||
} | ||
} | ||
if (aKey > bKey) { | ||
return 1; | ||
} | ||
return 0; | ||
} | ||
const commentnodeMap = new WeakMap(); // all nodes reference WeakMap for start and end range | ||
/** | ||
@@ -119,8 +126,45 @@ * Fixes sort order of prop types. | ||
* @param {Boolean=} callbacksLast whether or not to sort callbacks after everything else. | ||
* @param {Boolean=} noSortAlphabetically whether or not to disable alphabetical sorting of the elements. | ||
* @param {Boolean=} sortShapeProp whether or not to sort propTypes defined in PropTypes.shape. | ||
* @returns {Object|*|{range, text}} the sort order of the two elements. | ||
*/ | ||
function fixPropTypesSort(fixer, context, declarations, ignoreCase, requiredFirst, callbacksLast, sortShapeProp) { | ||
function fixPropTypesSort( | ||
fixer, | ||
context, | ||
declarations, | ||
ignoreCase, | ||
requiredFirst, | ||
callbacksLast, | ||
noSortAlphabetically, | ||
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) => { | ||
@@ -136,9 +180,12 @@ 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, noSortAlphabetically) | ||
); | ||
source = nodes.reduceRight((acc, attr, index) => { | ||
const sortedAttr = sortedAttributes[index]; | ||
let sortedAttrText = context.getSourceCode().getText(sortedAttr); | ||
const sourceCodeText = sourceCode.getText(); | ||
const commentNode = commentnodeMap.get(sortedAttr); | ||
let sortedAttrText = sourceCodeText.slice(commentNode.start, commentNode.end); | ||
if (sortShapeProp && isShapeProp(sortedAttr.value)) { | ||
@@ -154,3 +201,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); | ||
@@ -163,4 +210,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)); | ||
@@ -167,0 +214,0 @@ } |
@@ -7,5 +7,8 @@ /** | ||
const filter = require('es-iterator-helpers/Iterator.prototype.filter'); | ||
const some = require('es-iterator-helpers/Iterator.prototype.some'); | ||
function searchPropWrapperFunctions(name, propWrapperFunctions) { | ||
const splitName = name.split('.'); | ||
return Array.from(propWrapperFunctions).some((func) => { | ||
return some(propWrapperFunctions.values(), (func) => { | ||
if (splitName.length === 2 && func.object === splitName[0] && func.property === splitName[1]) { | ||
@@ -32,3 +35,3 @@ return true; | ||
const propWrapperFunctions = getPropWrapperFunctions(context); | ||
const exactPropWrappers = Array.from(propWrapperFunctions).filter((func) => func.exact === true); | ||
const exactPropWrappers = filter(propWrapperFunctions.values(), (func) => func.exact === true); | ||
return new Set(exactPropWrappers); | ||
@@ -35,0 +38,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'); | ||
@@ -536,3 +538,3 @@ const componentUtil = require('./componentUtil'); | ||
components.set(component ? component.node : node, { | ||
ignoreUnusedPropTypesValidation: true, | ||
ignoreUnusedPropTypesValidation: node.argument.type !== 'ObjectExpression', | ||
}); | ||
@@ -562,9 +564,9 @@ }, | ||
'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); | ||
}); | ||
}, | ||
}; | ||
}; |
@@ -9,4 +9,5 @@ /** | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const resolve = require('resolve'); | ||
const path = require('path'); | ||
const semver = require('semver'); | ||
@@ -13,0 +14,0 @@ const error = require('./error'); |
{ | ||
"name": "eslint-plugin-react", | ||
"version": "7.31.10", | ||
"version": "7.33.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,24 +30,26 @@ "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", | ||
"es-iterator-helpers": "^1.0.12", | ||
"estraverse": "^5.3.0", | ||
"jsx-ast-utils": "^2.4.1 || ^3.0.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", | ||
"semver": "^6.3.0", | ||
"string.prototype.matchall": "^4.0.7" | ||
"resolve": "^2.0.0-next.4", | ||
"semver": "^6.3.1", | ||
"string.prototype.matchall": "^4.0.8" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.19.3", | ||
"@babel/eslint-parser": "^7.19.1", | ||
"@babel/plugin-syntax-decorators": "^7.19.0", | ||
"@babel/plugin-syntax-do-expressions": "^7.18.6", | ||
"@babel/plugin-syntax-function-bind": "^7.18.6", | ||
"@babel/preset-react": "^7.18.6", | ||
"@babel/core": "^7.22.9", | ||
"@babel/eslint-parser": "^7.22.9", | ||
"@babel/plugin-syntax-decorators": "^7.22.5", | ||
"@babel/plugin-syntax-do-expressions": "^7.22.5", | ||
"@babel/plugin-syntax-function-bind": "^7.22.5", | ||
"@babel/preset-react": "^7.22.5", | ||
"@types/eslint": "=7.2.10", | ||
@@ -57,15 +59,16 @@ "@types/estree": "0.0.52", | ||
"@typescript-eslint/parser": "^2.34.0 || ^3.10.1 || ^4.0.0 || ^5.0.0", | ||
"aud": "^2.0.1", | ||
"aud": "^2.0.3", | ||
"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.3", | ||
"eslint-plugin-eslint-plugin": "^2.3.0 || ^3.5.3 || ^4.0.1 || ^5.0.5", | ||
"eslint-plugin-import": "^2.26.0", | ||
"eslint-plugin-import": "^2.27.5", | ||
"eslint-remote-tester": "^3.0.0", | ||
"eslint-remote-tester-repositories": "^0.0.7", | ||
"eslint-remote-tester-repositories": "^1.0.1", | ||
"eslint-scope": "^3.7.3", | ||
"espree": "^3.5.4", | ||
"istanbul": "^0.4.5", | ||
"ls-engines": "^0.7.0", | ||
"markdown-magic": "^2.6.1", | ||
"jackspeak": "=2.1.1", | ||
"ls-engines": "^0.8.1", | ||
"markdownlint-cli": "^0.8.0 || ^0.32.2", | ||
@@ -100,2 +103,3 @@ "mocha": "^5.2.0", | ||
"*.config.js", | ||
".eslint-doc-generatorrc.js", | ||
".eslintrc", | ||
@@ -102,0 +106,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*`) <a id="configuration"></a> | ||
@@ -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 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
794972
136
21403
421
3
17
30
+ Addedes-iterator-helpers@^1.0.12
+ Added@eslint-community/eslint-utils@4.4.1(transitive)
+ Added@eslint-community/regexpp@4.11.2(transitive)
+ Addedarray.prototype.tosorted@1.1.4(transitive)
+ Addedes-iterator-helpers@1.1.0(transitive)
+ Addedis-async-function@2.0.0(transitive)
+ Addedis-finalizationregistry@1.0.2(transitive)
+ Addedis-generator-function@1.0.10(transitive)
+ Addedis-map@2.0.3(transitive)
+ Addedis-set@2.0.3(transitive)
+ Addedis-weakmap@2.0.2(transitive)
+ Addedis-weakset@2.0.3(transitive)
+ Addediterator.prototype@1.1.3(transitive)
+ Addedreflect.getprototypeof@1.0.6(transitive)
+ Addedwhich-builtin-type@1.1.4(transitive)
+ Addedwhich-collection@1.0.2(transitive)
- Removed@eslint-community/eslint-utils@4.4.0(transitive)
- Removed@eslint-community/regexpp@4.11.1(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
Updatedsemver@^6.3.1