Socket
Socket
Sign inDemoInstall

eslint-plugin-react

Package Overview
Dependencies
Maintainers
2
Versions
208
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-react - npm Package Compare versions

Comparing version 7.34.4 to 7.35.0

lib/rules/jsx-props-no-spread-multi.js

46

configs/all.js
'use strict';
const fromEntries = require('object.fromentries');
const entries = require('object.entries');
const plugin = require('..');
const allRules = require('../lib/rules');
const legacyConfig = plugin.configs.all;
function filterRules(rules, predicate) {
return fromEntries(entries(rules).filter((entry) => predicate(entry[1])));
}
/**
* @param {object} rules - rules object mapping rule name to rule module
* @returns {Record<string, 2>}
*/
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 = {
plugins: {
/**
* @type {{
* deprecatedRules: Record<string, import('eslint').Rule.RuleModule>,
* rules: Record<string, import('eslint').Rule.RuleModule>,
* }}
*/
react: {
deprecatedRules,
rules: allRules,
},
},
rules: activeRulesConfig,
languageOptions: {
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
plugins: { react: plugin },
rules: legacyConfig.rules,
languageOptions: { parserOptions: legacyConfig.parserOptions },
};
// this is so the `languageOptions` property won't be warned in the new config system
Object.defineProperty(module.exports, 'languageOptions', { enumerable: false });
'use strict';
const all = require('./all');
const plugin = require('..');
module.exports = Object.assign({}, all, {
languageOptions: Object.assign({}, all.languageOptions, {
parserOptions: Object.assign({}, all.languageOptions.parserOptions, {
jsxPragma: null, // for @typescript/eslint-parser
}),
}),
rules: {
'react/react-in-jsx-scope': 0,
'react/jsx-uses-react': 0,
},
});
const legacyConfig = plugin.configs['jsx-runtime'];
// this is so the `languageOptions` property won't be warned in the new config system
module.exports = {
plugins: { react: plugin },
rules: legacyConfig.rules,
languageOptions: { parserOptions: legacyConfig.parserOptions },
};
Object.defineProperty(module.exports, 'languageOptions', { enumerable: false });
'use strict';
const all = require('./all');
const plugin = require('..');
module.exports = Object.assign({}, all, {
languageOptions: all.languageOptions,
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,
},
});
const legacyConfig = plugin.configs.recommended;
// this is so the `languageOptions` property won't be warned in the new config system
module.exports = {
plugins: { react: plugin },
rules: legacyConfig.rules,
languageOptions: { parserOptions: legacyConfig.parserOptions },
};
Object.defineProperty(module.exports, 'languageOptions', { enumerable: false });
'use strict';
const configAll = require('./configs/all');
const configRecommended = require('./configs/recommended');
const configRuntime = require('./configs/jsx-runtime');
const fromEntries = require('object.fromentries');
const entries = require('object.entries');
const allRules = require('./lib/rules');
function filterRules(rules, predicate) {
return fromEntries(entries(rules).filter((entry) => predicate(entry[1])));
}
/**
* @param {object} rules - rules object mapping rule name to rule module
* @returns {Record<string, 2>}
*/
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);
// for legacy config system

@@ -14,19 +30,81 @@ const plugins = [

module.exports = {
deprecatedRules: configAll.plugins.react.deprecatedRules,
const plugin = {
deprecatedRules,
rules: allRules,
configs: {
recommended: Object.assign({}, configRecommended, {
parserOptions: configRecommended.languageOptions.parserOptions,
recommended: {
plugins,
}),
all: Object.assign({}, configAll, {
parserOptions: configAll.languageOptions.parserOptions,
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,
}),
'jsx-runtime': Object.assign({}, configRuntime, {
parserOptions: configRuntime.languageOptions.parserOptions,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
rules: activeRulesConfig,
},
'jsx-runtime': {
plugins,
}),
parserOptions: {
ecmaFeatures: {
jsx: true,
},
jsxPragma: null, // for @typescript/eslint-parser
},
rules: {
'react/react-in-jsx-scope': 0,
'react/jsx-uses-react': 0,
},
},
},
};
plugin.configs.flat = {
recommended: {
plugins: { react: plugin },
rules: plugin.configs.recommended.rules,
languageOptions: { parserOptions: plugin.configs.recommended.parserOptions },
},
all: {
plugins: { react: plugin },
rules: plugin.configs.all.rules,
languageOptions: { parserOptions: plugin.configs.all.parserOptions },
},
'jsx-runtime': {
plugins: { react: plugin },
rules: plugin.configs['jsx-runtime'].rules,
languageOptions: { parserOptions: plugin.configs['jsx-runtime'].parserOptions },
},
};
module.exports = plugin;

@@ -8,2 +8,3 @@ /**

const minimatch = require('minimatch');
const docsUrl = require('../util/docsUrl');

@@ -74,2 +75,31 @@ const report = require('../util/report');

},
{
type: 'object',
properties: {
propNamePattern: { type: 'string' },
allowedFor: {
type: 'array',
uniqueItems: true,
items: { type: 'string' },
},
message: { type: 'string' },
},
additionalProperties: false,
},
{
type: 'object',
properties: {
propNamePattern: { type: 'string' },
disallowedFor: {
type: 'array',
uniqueItems: true,
minItems: 1,
items: { type: 'string' },
},
message: { type: 'string' },
},
required: ['disallowedFor'],
additionalProperties: false,
},
],

@@ -86,2 +116,4 @@ },

const propName = typeof value === 'string' ? value : value.propName;
const propPattern = value.propNamePattern;
const prop = propName || propPattern;
const options = {

@@ -91,8 +123,21 @@ allowList: typeof value === 'string' ? [] : (value.allowedFor || []),

message: typeof value === 'string' ? null : value.message,
isPattern: !!value.propNamePattern,
};
return [propName, options];
return [prop, options];
}));
function getPropOptions(prop) {
// Get config options having pattern
const propNamePatternArray = Array.from(forbid.entries()).filter((propEntry) => propEntry[1].isPattern);
// Match current prop with pattern options, return if matched
const propNamePattern = propNamePatternArray.find((propPatternVal) => minimatch(prop, propPatternVal[0]));
// Get options for matched propNamePattern
const propNamePatternOptions = propNamePattern && propNamePattern[1];
const options = forbid.get(prop) || propNamePatternOptions;
return options;
}
function isForbidden(prop, tagName) {
const options = forbid.get(prop);
const options = getPropOptions(prop);
if (!options) {

@@ -128,3 +173,3 @@ return false;

const customMessage = forbid.get(prop).message;
const customMessage = getPropOptions(prop).message;

@@ -131,0 +176,0 @@ report(context, customMessage || messages.propIsForbidden, !customMessage && 'propIsForbidden', {

@@ -5,3 +5,3 @@ 'use strict';

/** @type {Record<string, import('eslint').Rule.RuleModule>} */
/** @satisfies {Record<string, import('eslint').Rule.RuleModule>} */
module.exports = {

@@ -54,2 +54,3 @@ 'boolean-prop-naming': require('./boolean-prop-naming'),

'jsx-props-no-spreading': require('./jsx-props-no-spreading'),
'jsx-props-no-spread-multi': require('./jsx-props-no-spread-multi'),
'jsx-sort-default-props': require('./jsx-sort-default-props'),

@@ -56,0 +57,0 @@ 'jsx-sort-props': require('./jsx-sort-props'),

@@ -9,5 +9,7 @@ /**

const repeat = require('string.prototype.repeat');
const has = require('hasown');
const astUtil = require('../util/ast');
const docsUrl = require('../util/docsUrl');
const getSourceCode = require('../util/eslint').getSourceCode;
const report = require('../util/report');

@@ -22,4 +24,12 @@

matchIndent: 'Expected closing tag to match indentation of opening.',
alignWithOpening: 'Expected closing tag to be aligned with the line containing the opening tag',
};
const defaultOption = 'tag-aligned';
const optionMessageMap = {
'tag-aligned': 'matchIndent',
'line-aligned': 'alignWithOpening',
};
/** @type {import('eslint').Rule.RuleModule} */

@@ -36,5 +46,37 @@ module.exports = {

messages,
schema: [{
anyOf: [
{
enum: ['tag-aligned', 'line-aligned'],
},
{
type: 'object',
properties: {
location: {
enum: ['tag-aligned', 'line-aligned'],
},
},
additionalProperties: false,
},
],
}],
},
create(context) {
const config = context.options[0];
let option = defaultOption;
if (typeof config === 'string') {
option = config;
} else if (typeof config === 'object') {
if (has(config, 'location')) {
option = config.location;
}
}
function getIndentation(openingStartOfLine, opening) {
if (option === 'line-aligned') return openingStartOfLine.column;
if (option === 'tag-aligned') return opening.loc.start.column;
}
function handleClosingElement(node) {

@@ -44,4 +86,13 @@ if (!node.parent) {

}
const sourceCode = getSourceCode(context);
const opening = node.parent.openingElement || node.parent.openingFragment;
const openingLoc = sourceCode.getFirstToken(opening).loc.start;
const openingLine = sourceCode.lines[openingLoc.line - 1];
const openingStartOfLine = {
column: /^\s*/.exec(openingLine)[0].length,
line: openingLoc.line,
};
if (opening.loc.start.line === node.loc.start.line) {

@@ -51,9 +102,20 @@ return;

if (opening.loc.start.column === node.loc.start.column) {
if (
opening.loc.start.column === node.loc.start.column
&& option === 'tag-aligned'
) {
return;
}
if (
openingStartOfLine.column === node.loc.start.column
&& option === 'line-aligned'
) {
return;
}
const messageId = astUtil.isNodeFirstInLine(context, node)
? 'matchIndent'
? optionMessageMap[option]
: 'onOwnLine';
report(context, messages[messageId], messageId, {

@@ -63,3 +125,7 @@ node,

fix(fixer) {
const indent = repeat(' ', opening.loc.start.column);
const indent = repeat(
' ',
getIndentation(openingStartOfLine, opening)
);
if (astUtil.isNodeFirstInLine(context, node)) {

@@ -66,0 +132,0 @@ return fixer.replaceTextRange(

@@ -8,2 +8,3 @@ /**

const minimatch = require('minimatch');
const docsUrl = require('../util/docsUrl');

@@ -43,2 +44,7 @@ const getText = require('../util/eslint').getText;

checkInlineFunction: { type: 'boolean' },
ignoreComponentNames: {
type: 'array',
uniqueItems: true,
items: { type: 'string' },
},
},

@@ -56,2 +62,7 @@ additionalProperties: false,

checkInlineFunction: { type: 'boolean' },
ignoreComponentNames: {
type: 'array',
uniqueItems: true,
items: { type: 'string' },
},
},

@@ -69,2 +80,7 @@ additionalProperties: false,

checkInlineFunction: { type: 'boolean' },
ignoreComponentNames: {
type: 'array',
uniqueItems: true,
items: { type: 'string' },
},
},

@@ -85,2 +101,12 @@ additionalProperties: false,

},
{
type: 'object',
properties: {
ignoreComponentNames: {
type: 'array',
uniqueItems: true,
items: { type: 'string' },
},
},
},
],

@@ -119,4 +145,13 @@ }],

const ignoreComponentNames = configuration.ignoreComponentNames || [];
return {
JSXAttribute(node) {
const componentName = node.parent.name.name;
const isComponentNameIgnored = ignoreComponentNames.some((ignoredComponentNamePattern) => {
const isIgnored = minimatch(componentName, ignoredComponentNamePattern);
return isIgnored;
});
if (

@@ -133,2 +168,3 @@ !node.value

)
|| isComponentNameIgnored
) {

@@ -135,0 +171,0 @@ return;

@@ -91,3 +91,3 @@ /**

function findJSXElementOrFragment(variables, name, previousReferences) {
function findJSXElementOrFragment(startNode, name, previousReferences) {
function find(refs, prevRefs) {

@@ -101,3 +101,3 @@ for (let i = refs.length - 1; i >= 0; i--) {

|| ((writeExpr && writeExpr.type === 'Identifier')
&& findJSXElementOrFragment(variables, writeExpr.name, prevRefs));
&& findJSXElementOrFragment(startNode, writeExpr.name, prevRefs));
}

@@ -109,3 +109,3 @@ }

const variable = variableUtil.getVariable(variables, name);
const variable = variableUtil.getVariableFromContext(context, startNode, name);
if (variable && variable.references) {

@@ -156,4 +156,3 @@ const containDuplicates = previousReferences.some((ref) => includes(variable.references, ref));

const variables = variableUtil.variablesInScope(context, node);
const element = findJSXElementOrFragment(variables, node.expression.name, []);
const element = findJSXElementOrFragment(node, node.expression.name, []);

@@ -160,0 +159,0 @@ if (element) {

@@ -164,4 +164,3 @@ /**

}
const variables = variableUtil.variablesInScope(context, node);
const leftSideVar = variableUtil.getVariable(variables, leftSide.name);
const leftSideVar = variableUtil.getVariableFromContext(context, node, leftSide.name);
if (leftSideVar) {

@@ -168,0 +167,0 @@ const leftSideValue = leftSideVar.defs

@@ -100,4 +100,3 @@ /**

const variable = variableUtil
.variablesInScope(context, node)
.find((item) => item.name === name);
.getVariableFromContext(context, node, name);

@@ -104,0 +103,0 @@ if (!variable || !variable.defs[0] || !variable.defs[0].node) {

@@ -35,4 +35,3 @@ /**

function findSpreadVariable(node, name) {
return variableUtil.variablesInScope(context, node)
.find((item) => item.name === name);
return variableUtil.getVariableFromContext(context, node, name);
}

@@ -132,4 +131,3 @@ /**

if (props.type === 'Identifier') {
const variable = variableUtil.variablesInScope(context, node)
.find((item) => item.name === props.name);
const variable = variableUtil.getVariableFromContext(context, node, props.name);
if (variable && variable.defs.length && variable.defs[0].node.init) {

@@ -136,0 +134,0 @@ props = variable.defs[0].node.init;

@@ -10,2 +10,3 @@ /**

const fromEntries = require('object.fromentries/polyfill')();
const minimatch = require('minimatch');

@@ -59,9 +60,28 @@ const docsUrl = require('../util/docsUrl');

schema: [],
schema: [{
type: 'object',
properties: {
customComponentNames: {
items: {
type: 'string',
},
minItems: 0,
type: 'array',
uniqueItems: true,
},
},
}],
},
create(context) {
const configuration = context.options[0] || {};
const customComponentNames = configuration.customComponentNames || [];
return {
JSXAttribute(node) {
if (jsxUtil.isDOMComponent(node.parent) && isDangerous(node.name.name)) {
const functionName = node.parent.name.name;
const enableCheckingCustomComponent = customComponentNames.some((name) => minimatch(functionName, name));
if ((enableCheckingCustomComponent || jsxUtil.isDOMComponent(node.parent)) && isDangerous(node.name.name)) {
report(context, messages.dangerousProp, 'dangerousProp', {

@@ -68,0 +88,0 @@ node,

@@ -11,3 +11,2 @@ /**

const report = require('../util/report');
const getMessageData = require('../util/message');

@@ -228,2 +227,3 @@ // ------------------------------------------------------------------------------

/* eslint-disable eslint-plugin/no-unused-message-ids -- false positives, these messageIds are used */
const messages = {

@@ -269,11 +269,7 @@ emptyIsMeaningless: 'An empty “{{attributeName}}” attribute is meaningless.',

data,
suggest: [
Object.assign(
getMessageData('suggestRemoveNonString', messages.suggestRemoveNonString),
{
data,
fix(fixer) { return fixer.remove(parentNode); },
}
),
],
suggest: [{
messageId: 'suggestRemoveNonString',
data,
fix(fixer) { return fixer.remove(parentNode); },
}],
});

@@ -289,11 +285,7 @@ return;

data,
suggest: [
Object.assign(
getMessageData('suggestRemoveEmpty', messages.suggestRemoveEmpty),
{
data,
fix(fixer) { return fixer.remove(node.parent); },
}
),
],
suggest: [{
messageId: 'suggestRemoveEmpty',
data,
fix(fixer) { return fixer.remove(node.parent); },
}],
});

@@ -314,14 +306,12 @@ return;

const suggest = [{
messageId: 'suggestRemoveInvalid',
data,
fix(fixer) { return fixer.removeRange(singlePart.range); },
}];
report(context, messages.neverValid, 'neverValid', {
node,
data,
suggest: [
Object.assign(
getMessageData('suggestRemoveInvalid', messages.suggestRemoveInvalid),
{
data,
fix(fixer) { return fixer.removeRange(singlePart.range); },
}
),
],
suggest,
});

@@ -335,14 +325,12 @@ } else if (!allowedTags.has(parentNodeName)) {

const suggest = [{
messageId: 'suggestRemoveInvalid',
data,
fix(fixer) { return fixer.removeRange(singlePart.range); },
}];
report(context, messages.notValidFor, 'notValidFor', {
node,
data,
suggest: [
Object.assign(
getMessageData('suggestRemoveInvalid', messages.suggestRemoveInvalid),
{
data,
fix(fixer) { return fixer.removeRange(singlePart.range); },
}
),
],
suggest,
});

@@ -384,15 +372,13 @@ }

for (const whitespacePart of whitespaceParts) {
const data = { attributeName };
if (whitespacePart.range[0] === (node.range[0] + 1) || whitespacePart.range[1] === (node.range[1] - 1)) {
report(context, messages.spaceDelimited, 'spaceDelimited', {
node,
data: { attributeName },
suggest: [
Object.assign(
getMessageData('suggestRemoveWhitespaces', messages.suggestRemoveWhitespaces),
{
data: { attributeName },
fix(fixer) { return fixer.removeRange(whitespacePart.range); },
}
),
],
data,
suggest: [{
messageId: 'suggestRemoveWhitespaces',
data,
fix(fixer) { return fixer.removeRange(whitespacePart.range); },
}],
});

@@ -402,12 +388,8 @@ } else if (whitespacePart.value !== '\u0020') {

node,
data: { attributeName },
suggest: [
Object.assign(
getMessageData('suggestRemoveWhitespaces', messages.suggestRemoveWhitespaces),
{
data: { attributeName },
fix(fixer) { return fixer.replaceTextRange(whitespacePart.range, '\u0020'); },
}
),
],
data,
suggest: [{
messageId: 'suggestRemoveWhitespaces',
data,
fix(fixer) { return fixer.replaceTextRange(whitespacePart.range, '\u0020'); },
}],
});

@@ -437,11 +419,7 @@ }

data,
suggest: [
Object.assign(
getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault),
{
data,
fix(fixer) { return fixer.remove(node); },
}
),
],
suggest: [{
messageId: 'suggestRemoveDefault',
data,
fix(fixer) { return fixer.remove(node); },
}],
});

@@ -459,8 +437,7 @@ return;

data,
suggest: [
Object.assign(
getMessageData('suggestRemoveEmpty', messages.suggestRemoveEmpty),
{ data, fix }
),
],
suggest: [{
messageId: 'suggestRemoveEmpty',
data,
fix,
}],
});

@@ -488,8 +465,7 @@ return;

data,
suggest: [
Object.assign(
getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault),
{ data, fix }
),
],
suggest: [{
messageId: 'suggestRemoveDefault',
data,
fix,
}],
});

@@ -502,8 +478,7 @@ } else if (node.value.expression.type === 'Identifier' && node.value.expression.name === 'undefined') {

data,
suggest: [
Object.assign(
getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault),
{ data, fix }
),
],
suggest: [{
messageId: 'suggestRemoveDefault',
data,
fix,
}],
});

@@ -538,11 +513,7 @@ }

data,
suggest: [
Object.assign(
getMessageData('suggestRemoveInvalid', messages.suggestRemoveInvalid),
{
data,
fix(fixer) { return fixer.replaceText(value, value.raw.replace(value.value, '')); },
}
),
],
suggest: [{
messageId: 'suggestRemoveInvalid',
data,
fix(fixer) { return fixer.replaceText(value, value.raw.replace(value.value, '')); },
}],
});

@@ -549,0 +520,0 @@ } else if (!validTagSet.has(node.arguments[0].value)) {

@@ -40,4 +40,3 @@ /**

function checkIfReactIsInScope(node) {
const variables = variableUtil.variablesInScope(context, node);
if (variableUtil.findVariable(variables, pragma)) {
if (variableUtil.getVariableFromContext(context, node, pragma)) {
return;

@@ -44,0 +43,0 @@ }

@@ -27,2 +27,9 @@ /**

function isPropWithNoDefaulVal(prop) {
if (prop.type === 'RestElement' || prop.type === 'ExperimentalRestProperty') {
return false;
}
return prop.value.type !== 'AssignmentPattern';
}
/** @type {import('eslint').Rule.RuleModule} */

@@ -138,12 +145,20 @@ module.exports = {

} else if (props.type === 'ObjectPattern') {
// Filter required props with default value and report error
props.properties.filter((prop) => {
if (prop.type === 'RestElement' || prop.type === 'ExperimentalRestProperty') {
return false;
}
const propType = propTypes[prop.key.name];
if (!propType || propType.isRequired) {
return false;
}
return prop.value.type !== 'AssignmentPattern';
const propName = prop && prop.key && prop.key.name;
const isPropRequired = propTypes[propName] && propTypes[propName].isRequired;
return propTypes[propName] && isPropRequired && !isPropWithNoDefaulVal(prop);
}).forEach((prop) => {
report(context, messages.noDefaultWithRequired, 'noDefaultWithRequired', {
node: prop,
data: { name: prop.key.name },
});
});
// Filter non required props with no default value and report error
props.properties.filter((prop) => {
const propName = prop && prop.key && prop.key.name;
const isPropRequired = propTypes[propName] && propTypes[propName].isRequired;
return propTypes[propName] && !isPropRequired && isPropWithNoDefaulVal(prop);
}).forEach((prop) => {
report(context, messages.shouldAssignObjectDefault, 'shouldAssignObjectDefault', {

@@ -150,0 +165,0 @@ node: prop,

@@ -94,4 +94,3 @@ /**

function findVariableByName(node, name) {
const variable = variableUtil.variablesInScope(context, node)
.find((item) => item.name === name);
const variable = variableUtil.getVariableFromContext(context, node, name);

@@ -98,0 +97,0 @@ if (!variable || !variable.defs[0] || !variable.defs[0].node) {

@@ -132,3 +132,4 @@ /**

noSortAlphabetically,
sortShapeProp
sortShapeProp,
checkTypes
);

@@ -135,0 +136,0 @@ }

@@ -64,4 +64,3 @@ /**

function checkIdentifiers(node) {
const variable = variableUtil.variablesInScope(context, node)
.find((item) => item.name === node.name);
const variable = variableUtil.getVariableFromContext(context, node, node.name);

@@ -68,0 +67,0 @@ if (!variable || !variable.defs[0] || !variable.defs[0].node.init) {

@@ -14,5 +14,6 @@ import eslint from 'eslint';

type JSXFragment = ASTNode;
type JSXOpeningElement = ASTNode;
type JSXSpreadAttribute = ASTNode;
type Context = eslint.Rule.RuleContext
type Context = eslint.Rule.RuleContext;

@@ -19,0 +20,0 @@ type TypeDeclarationBuilder = (annotation: ASTNode, parentName: string, seen: Set<typeof annotation>) => object;

@@ -672,10 +672,3 @@ /**

}
let variableInScope;
const variables = variableUtil.variablesInScope(context, node);
for (i = 0, j = variables.length; i < j; i++) {
if (variables[i].name === variableName) {
variableInScope = variables[i];
break;
}
}
const variableInScope = variableUtil.getVariableFromContext(context, node, variableName);
if (!variableInScope) {

@@ -682,0 +675,0 @@ return null;

@@ -16,4 +16,3 @@ 'use strict';

const pragma = pragmaUtil.getFromContext(context);
const variables = variableUtil.variablesInScope(context, node);
const variableInScope = variableUtil.getVariable(variables, variable);
const variableInScope = variableUtil.getVariableFromContext(context, node, variable);
if (variableInScope) {

@@ -20,0 +19,0 @@ const latestDef = variableUtil.getLatestVariableDefinition(variableInScope);

@@ -968,4 +968,3 @@ /**

case 'Identifier': {
const firstMatchingVariable = variableUtil.variablesInScope(context, node)
.find((variableInScope) => variableInScope.name === propTypes.name);
const firstMatchingVariable = variableUtil.getVariableFromContext(context, node, propTypes.name);
if (firstMatchingVariable) {

@@ -972,0 +971,0 @@ const defInScope = firstMatchingVariable.defs[firstMatchingVariable.defs.length - 1];

@@ -129,2 +129,3 @@ /**

* @param {Boolean=} sortShapeProp whether or not to sort propTypes defined in PropTypes.shape.
* @param {Boolean=} checkTypes whether or not sorting of prop type definitions are checked.
* @returns {Object|*|{range, text}} the sort order of the two elements.

@@ -140,3 +141,4 @@ */

noSortAlphabetically,
sortShapeProp
sortShapeProp,
checkTypes
) {

@@ -188,2 +190,3 @@ function sortInSource(allNodes, source) {

const sourceCodeText = getText(context);
let separator = '';
source = nodes.reduceRight((acc, attr, index) => {

@@ -193,2 +196,6 @@ const sortedAttr = sortedAttributes[index];

let sortedAttrText = sourceCodeText.slice(commentNode.start, commentNode.end);
const sortedAttrTextLastChar = sortedAttrText[sortedAttrText.length - 1];
if (!separator && [';', ','].some((allowedSep) => sortedAttrTextLastChar === allowedSep)) {
separator = sortedAttrTextLastChar;
}
if (sortShapeProp && isShapeProp(sortedAttr.value)) {

@@ -204,3 +211,4 @@ const shape = getShapeProperties(sortedAttr.value);

}
return `${acc.slice(0, commentnodeMap.get(attr).start)}${sortedAttrText}${acc.slice(commentnodeMap.get(attr).end)}`;
const sortedAttrTextVal = checkTypes && !sortedAttrText.endsWith(separator) ? `${sortedAttrText}${separator}` : sortedAttrText;
return `${acc.slice(0, commentnodeMap.get(attr).start)}${sortedAttrTextVal}${acc.slice(commentnodeMap.get(attr).end)}`;
}, source);

@@ -207,0 +215,0 @@ });

@@ -8,3 +8,2 @@ /**

const toReversed = require('array.prototype.toreversed');
const getScope = require('./eslint').getScope;

@@ -33,26 +32,29 @@

/**
* List all variable in a given scope
* Searches for a variable in the given scope.
*
* Contain a patch for babel-eslint to avoid https://github.com/babel/babel-eslint/issues/21
*
* @param {Object} context The current rule context.
* @param {ASTNode} node The node to start looking from.
* @returns {Array} The variables list
* @param {string} name The name of the variable to search.
* @returns {Object | undefined} Variable if the variable was found, undefined if not.
*/
function variablesInScope(context, node) {
function getVariableFromContext(context, node, name) {
let scope = getScope(context, node);
let variables = scope.variables;
while (scope.type !== 'global') {
while (scope) {
let variable = getVariable(scope.variables, name);
if (!variable && scope.childScopes.length) {
variable = getVariable(scope.childScopes[0].variables, name);
if (!variable && scope.childScopes[0].childScopes.length) {
variable = getVariable(scope.childScopes[0].childScopes[0].variables, name);
}
}
if (variable) {
return variable;
}
scope = scope.upper;
variables = scope.variables.concat(variables);
}
if (scope.childScopes.length) {
variables = scope.childScopes[0].variables.concat(variables);
if (scope.childScopes[0].childScopes.length) {
variables = scope.childScopes[0].childScopes[0].variables.concat(variables);
}
}
return toReversed(variables);
return undefined;
}

@@ -68,3 +70,3 @@

function findVariableByName(context, node, name) {
const variable = getVariable(variablesInScope(context, node), name);
const variable = getVariableFromContext(context, node, name);

@@ -99,4 +101,4 @@ if (!variable || !variable.defs[0] || !variable.defs[0].node) {

getVariable,
variablesInScope,
getVariableFromContext,
getLatestVariableDefinition,
};

@@ -15,2 +15,4 @@ /**

const ULTIMATE_LATEST_SEMVER = '999.999.999';
let warnedForMissingVersion = false;

@@ -48,2 +50,33 @@

function convertConfVerToSemver(confVer) {
const fullSemverString = /^[0-9]+\.[0-9]+$/.test(confVer) ? `${confVer}.0` : confVer;
return semver.coerce(fullSemverString.split('.').map((part) => Number(part)).join('.'));
}
let defaultVersion = ULTIMATE_LATEST_SEMVER;
function resetDefaultVersion() {
defaultVersion = ULTIMATE_LATEST_SEMVER;
}
function readDefaultReactVersionFromContext(context) {
// .eslintrc shared settings (https://eslint.org/docs/user-guide/configuring#adding-shared-settings)
if (context.settings && context.settings.react && context.settings.react.defaultVersion) {
let settingsDefaultVersion = context.settings.react.defaultVersion;
if (typeof settingsDefaultVersion !== 'string') {
error('Warning: default React version specified in eslint-pluigin-react-settings must be a string; '
+ `got "${typeof settingsDefaultVersion}"`);
}
settingsDefaultVersion = String(settingsDefaultVersion);
const result = convertConfVerToSemver(settingsDefaultVersion);
if (result) {
defaultVersion = result.version;
} else {
error(`Warning: React version specified in eslint-plugin-react-settings must be a valid semver version, or "detect"; got “${settingsDefaultVersion}”. Falling back to latest version as default.`);
}
} else {
defaultVersion = ULTIMATE_LATEST_SEMVER;
}
}
// TODO, semver-major: remove context fallback

@@ -65,7 +98,10 @@ function detectReactVersion(context) {

if (!warnedForMissingVersion) {
error('Warning: React version was set to "detect" in eslint-plugin-react settings, '
+ 'but the "react" package is not installed. Assuming latest React version for linting.');
let sentence2 = 'Assuming latest React version for linting.';
if (defaultVersion !== ULTIMATE_LATEST_SEMVER) {
sentence2 = `Assuming default React version for linting: "${defaultVersion}".`;
}
error(`Warning: React version was set to "detect" in eslint-plugin-react settings, but the "react" package is not installed. ${sentence2}`);
warnedForMissingVersion = true;
}
cachedDetectedReactVersion = '999.999.999';
cachedDetectedReactVersion = defaultVersion;
return cachedDetectedReactVersion;

@@ -77,5 +113,4 @@ }

const defaultVersion = '999.999.999';
function getReactVersionFromContext(context) {
readDefaultReactVersionFromContext(context);
let confVer = defaultVersion;

@@ -98,4 +133,4 @@ // .eslintrc shared settings (https://eslint.org/docs/user-guide/configuring#adding-shared-settings)

}
confVer = /^[0-9]+\.[0-9]+$/.test(confVer) ? `${confVer}.0` : confVer;
const result = semver.coerce(confVer.split('.').map((part) => Number(part)).join('.'));
const result = convertConfVerToSemver(confVer);
if (!result) {

@@ -119,3 +154,3 @@ error(`Warning: React version specified in eslint-plugin-react-settings must be a valid semver version, or "detect"; got “${confVer}”`);

+ 'but the "flow-bin" package is not installed. Assuming latest Flow version for linting.');
return '999.999.999';
return ULTIMATE_LATEST_SEMVER;
}

@@ -142,4 +177,4 @@ throw e;

}
confVer = /^[0-9]+\.[0-9]+$/.test(confVer) ? `${confVer}.0` : confVer;
const result = semver.coerce(confVer.split('.').map((part) => Number(part)).join('.'));
const result = convertConfVerToSemver(confVer);
if (!result) {

@@ -168,2 +203,3 @@ error(`Warning: Flow version specified in eslint-plugin-react-settings must be a valid semver version, or "detect"; got “${confVer}”`);

resetDetectedVersion,
resetDefaultVersion,
};
{
"name": "eslint-plugin-react",
"version": "7.34.4",
"version": "7.35.0",
"author": "Yannick Croissant <yannick.croissant+npm@gmail.com>",

@@ -18,3 +18,3 @@ "description": "React specific linting rules for ESLint",

"type-check": "tsc",
"unit-test": "istanbul cover node_modules/mocha/bin/_mocha tests/lib/**/*.js tests/util/**/*.js tests/index.js",
"unit-test": "istanbul cover node_modules/mocha/bin/_mocha tests/lib/**/*.js tests/util/**/*.js tests/index.js tests/flat-config.js",
"update:eslint-docs": "eslint-doc-generator"

@@ -32,3 +32,2 @@ },

"array.prototype.flatmap": "^1.3.2",
"array.prototype.toreversed": "^1.1.2",
"array.prototype.tosorted": "^1.1.4",

@@ -51,4 +50,4 @@ "doctrine": "^2.1.0",

"devDependencies": {
"@babel/core": "^7.24.7",
"@babel/eslint-parser": "^7.24.7",
"@babel/core": "^7.24.9",
"@babel/eslint-parser": "^7.24.8",
"@babel/plugin-syntax-decorators": "^7.24.7",

@@ -64,3 +63,3 @@ "@babel/plugin-syntax-do-expressions": "^7.24.7",

"babel-eslint": "^8 || ^9 || ^10.1.0",
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8",
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7",
"eslint-config-airbnb-base": "^15.0.0",

@@ -86,3 +85,3 @@ "eslint-doc-generator": "^1.7.1",

"peerDependencies": {
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
},

@@ -89,0 +88,0 @@ "engines": {

@@ -45,3 +45,5 @@ # `eslint-plugin-react` <sup>[![Version Badge][npm-version-svg]][package-url]</sup>

// You can also use `16.0`, `16.3`, etc, if you want to override the detected value.
// It will default to "latest" and warn if missing, and to "detect" in the future
// Defaults to the "defaultVersion" setting and warns if missing, and to "detect" in the future
"defaultVersion": "", // Default React version to use when the version you have installed cannot be detected.
// If not provided, defaults to the latest React version.
"flowVersion": "0.53" // Flow version

@@ -207,23 +209,18 @@ },

<!-- markdownlint-disable-next-line no-duplicate-heading -->
### Shareable configs
### Flat Configs
There're also 3 shareable configs.
This plugin exports 3 flat configs:
- `eslint-plugin-react/configs/all`
- `eslint-plugin-react/configs/recommended`
- `eslint-plugin-react/configs/jsx-runtime`
- `flat.all`
- `flat.recommended`
- `flat['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.
The flat configs are available via the root plugin import. They will configure the plugin under the `react/` namespace and enable JSX in [`languageOptions.parserOptions`](https://eslint.org/docs/latest/use/configure/language-options#specifying-parser-options).
**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');
const reactPlugin = require('eslint-plugin-react');
module.exports = [
reactRecommended, // This is not a plugin object, but a shareable config object
reactPlugin.configs.flat.recommended, // This is not a plugin object, but a shareable config object

@@ -239,3 +236,3 @@ ];

```js
const reactRecommended = require('eslint-plugin-react/configs/recommended');
const reactPlugin = require('eslint-plugin-react');
const globals = require('globals');

@@ -247,5 +244,5 @@

files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
...reactRecommended,
...reactPlugin.configs.flat.recommended,
languageOptions: {
...reactRecommended.languageOptions,
...reactPlugin.configs.flat.recommended.languageOptions,
globals: {

@@ -264,3 +261,3 @@ ...globals.serviceworker,

```js
const reactRecommended = require('eslint-plugin-react/configs/recommended');
const reactPlugin = require('eslint-plugin-react');
const globals = require('globals');

@@ -272,3 +269,3 @@

files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
...reactRecommended,
...reactPlugin.configs.flat.recommended,
},

@@ -347,2 +344,3 @@ {

| [jsx-props-no-multi-spaces](docs/rules/jsx-props-no-multi-spaces.md) | Disallow multiple spaces between inline JSX props | | | 🔧 | | |
| [jsx-props-no-spread-multi](docs/rules/jsx-props-no-spread-multi.md) | Disallow JSX prop spreading the same identifier multiple times | | | | | |
| [jsx-props-no-spreading](docs/rules/jsx-props-no-spreading.md) | Disallow JSX prop spreading | | | | | |

@@ -349,0 +347,0 @@ | [jsx-sort-default-props](docs/rules/jsx-sort-default-props.md) | Enforce defaultProps declarations alphabetical sorting | | | | | ❌ |

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc