eslint-plugin-react
Advanced tools
Comparing version 7.32.2 to 7.33.2
@@ -11,3 +11,2 @@ /** | ||
const report = require('../util/report'); | ||
const testReactVersion = require('../util/version').testReactVersion; | ||
@@ -99,4 +98,2 @@ const DEFAULT_OPTION = 'always'; | ||
// set to save renamed var of useContext | ||
const contextSet = new Set(); | ||
/** | ||
@@ -136,3 +133,3 @@ * @param {ASTNode} node We expect either an ArrowFunctionExpression, | ||
const contextName = sfcParams.contextName(); | ||
// props.aProp | ||
// props.aProp || context.aProp | ||
const isPropUsed = ( | ||
@@ -151,17 +148,2 @@ (propsName && node.object.name === propsName) | ||
} | ||
// const foo = useContext(aContext); | ||
// foo.aProp | ||
const isContextUsed = contextSet.has(node.object.name) && !isAssignmentLHS(node); | ||
const optional = node.optional | ||
// the below is for the old typescript-eslint parser | ||
|| context.getSourceCode().getText(node).slice(node.object.range[1] - node.range[0], node.object.range[1] - node.range[0] + 1) === '?'; | ||
if (isContextUsed && configuration === 'always' && !optional) { | ||
report(context, messages.useDestructAssignment, 'useDestructAssignment', { | ||
node, | ||
data: { | ||
type: node.object.name, | ||
}, | ||
}); | ||
} | ||
} | ||
@@ -201,5 +183,4 @@ | ||
const hasHooks = testReactVersion(context, '>= 16.9'); | ||
return { | ||
return { | ||
FunctionDeclaration: handleStatelessComponent, | ||
@@ -239,9 +220,4 @@ | ||
const destructuring = (node.init && node.id && node.id.type === 'ObjectPattern'); | ||
const identifier = (node.init && node.id && node.id.type === 'Identifier'); | ||
// let {foo} = props; | ||
const destructuringSFC = destructuring && node.init.name === 'props'; | ||
// let {foo} = useContext(aContext); | ||
const destructuringUseContext = hasHooks && destructuring && node.init.callee && node.init.callee.name === 'useContext'; | ||
// let foo = useContext(aContext); | ||
const assignUseContext = hasHooks && identifier && node.init.callee && node.init.callee.name === 'useContext'; | ||
const destructuringSFC = destructuring && (node.init.name === 'props' || node.init.name === 'context'); | ||
// let {foo} = this.props; | ||
@@ -252,15 +228,2 @@ const destructuringClass = destructuring && node.init.object && node.init.object.type === 'ThisExpression' && ( | ||
if (SFCComponent && assignUseContext) { | ||
contextSet.add(node.id.name); | ||
} | ||
if (SFCComponent && destructuringUseContext && configuration === 'never') { | ||
report(context, messages.noDestructAssignment, 'noDestructAssignment', { | ||
node, | ||
data: { | ||
type: node.init.callee.name, | ||
}, | ||
}); | ||
} | ||
if (SFCComponent && destructuringSFC && configuration === 'never') { | ||
@@ -267,0 +230,0 @@ report(context, messages.noDestructAssignment, 'noDestructAssignment', { |
@@ -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: { | ||
anyOf: [{ | ||
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 @@ |
@@ -187,3 +187,3 @@ /** | ||
? expression.quasis[0].value.raw | ||
: expression.raw.substring(1, expression.raw.length - 1) | ||
: expression.raw.slice(1, -1) | ||
}"`; | ||
@@ -226,3 +226,3 @@ } else if (jsxUtil.isJSX(expression)) { | ||
? `{"${escapeDoubleQuotes(escapeBackslashes( | ||
literalNode.raw.substring(1, literalNode.raw.length - 1) | ||
literalNode.raw.slice(1, -1) | ||
))}"}` | ||
@@ -229,0 +229,0 @@ : wrapWithCurlyBraces(literalNode.raw); |
@@ -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) { |
@@ -8,2 +8,5 @@ /** | ||
const find = require('es-iterator-helpers/Iterator.prototype.find'); | ||
const from = require('es-iterator-helpers/Iterator.from'); | ||
const docsUrl = require('../util/docsUrl'); | ||
@@ -139,3 +142,3 @@ const report = require('../util/report'); | ||
const validStrategies = new Set(config.validStrategies || DEFAULT_VALID_STRATEGIES); | ||
const fixStrategy = Array.from(validStrategies)[0]; | ||
const fixStrategy = find(from(validStrategies), () => true); | ||
@@ -142,0 +145,0 @@ return { |
@@ -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() { |
@@ -253,3 +253,3 @@ /** | ||
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 +270,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 +276,0 @@ } |
@@ -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; | ||
@@ -197,3 +233,3 @@ }); | ||
node.specifiers.filter(((s) => s.imported)).forEach((specifier) => { | ||
checkDeprecation(node, `${MODULES[node.source.value][0]}.${specifier.imported.name}`); | ||
checkDeprecation(node, `${MODULES[node.source.value][0]}.${specifier.imported.name}`, specifier); | ||
}); | ||
@@ -218,3 +254,3 @@ }, | ||
node.id.properties.filter((p) => p.type !== 'RestElement' && p.key).forEach((property) => { | ||
checkDeprecation(node, `${reactModuleName || pragma}.${property.key.name}`); | ||
checkDeprecation(node, `${reactModuleName || pragma}.${property.key.name}`, property); | ||
}); | ||
@@ -221,0 +257,0 @@ }, |
@@ -222,4 +222,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'])], | ||
]); | ||
@@ -393,3 +394,3 @@ const messages = { | ||
report(context, messages.onlyMeaningfulFor, 'onlyMeaningfulFor', { | ||
node, | ||
node: node.name, | ||
data: { | ||
@@ -413,3 +414,3 @@ attributeName: attribute, | ||
report(context, messages.emptyIsMeaningless, 'emptyIsMeaningless', { | ||
node, | ||
node: node.name, | ||
data: { attributeName: attribute }, | ||
@@ -440,3 +441,3 @@ suggest: [ | ||
report(context, messages.onlyStrings, 'onlyStrings', { | ||
node, | ||
node: node.value, | ||
data: { attributeName: attribute }, | ||
@@ -452,3 +453,3 @@ suggest: [ | ||
report(context, messages.onlyStrings, 'onlyStrings', { | ||
node, | ||
node: node.value, | ||
data: { attributeName: attribute }, | ||
@@ -539,3 +540,3 @@ suggest: [ | ||
report(context, messages.onlyMeaningfulFor, 'onlyMeaningfulFor', { | ||
node, | ||
node: prop.key, | ||
data: { | ||
@@ -542,0 +543,0 @@ attributeName: attribute, |
@@ -96,3 +96,3 @@ /** | ||
} | ||
rawLine = rawLine.substring(start, end); | ||
rawLine = rawLine.slice(start, end); | ||
for (let j = 0; j < entities.length; j++) { | ||
@@ -99,0 +99,0 @@ for (let index = 0; index < rawLine.length; index++) { |
@@ -52,2 +52,3 @@ /** | ||
'svg', | ||
'symbol', | ||
'text', | ||
@@ -85,3 +86,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'], | ||
@@ -88,0 +89,0 @@ onLoadedMetadata: ['audio', 'video'], |
@@ -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 @@ } |
@@ -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 @@ |
@@ -19,2 +19,6 @@ /** | ||
function isTypescriptPropertyType(node) { | ||
return node.type === 'TSPropertySignature'; | ||
} | ||
function isCovariant(node) { | ||
@@ -31,2 +35,10 @@ return (node.variance && node.variance.kind === 'plus') | ||
function isReadonly(node) { | ||
return ( | ||
node.typeAnnotation | ||
&& node.typeAnnotation.parent | ||
&& node.typeAnnotation.parent.readonly | ||
); | ||
} | ||
// ------------------------------------------------------------------------------ | ||
@@ -55,36 +67,53 @@ // Rule Definition | ||
create: Components.detect((context, components) => ({ | ||
'Program:exit'() { | ||
flatMap( | ||
values(components.list()), | ||
(component) => component.declaredPropTypes || [] | ||
).forEach((declaredPropTypes) => { | ||
Object.keys(declaredPropTypes).forEach((propName) => { | ||
const prop = declaredPropTypes[propName]; | ||
create: Components.detect((context, components) => { | ||
function reportReadOnlyProp(prop, propName, fixer) { | ||
report(context, messages.readOnlyProp, 'readOnlyProp', { | ||
node: prop.node, | ||
data: { | ||
name: propName, | ||
}, | ||
fix: fixer, | ||
}); | ||
} | ||
if (!prop.node || !isFlowPropertyType(prop.node)) { | ||
return; | ||
} | ||
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 (!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, '+'); | ||
} | ||
if (isFlowPropertyType(prop.node)) { | ||
if (!isCovariant(prop.node)) { | ||
reportReadOnlyProp(prop, propName, (fixer) => { | ||
if (!prop.node.variance) { | ||
// Insert covariance | ||
return fixer.insertTextBefore(prop.node, '+'); | ||
} | ||
// Replace contravariance with covariance | ||
return fixer.replaceText(prop.node.variance, '+'); | ||
}, | ||
}); | ||
} | ||
// Replace contravariance with covariance | ||
return fixer.replaceText(prop.node.variance, '+'); | ||
}); | ||
} | ||
return; | ||
} | ||
if (isTypescriptPropertyType(prop.node)) { | ||
if (!isReadonly(prop.node)) { | ||
reportReadOnlyProp(prop, propName, (fixer) => ( | ||
fixer.insertTextBefore(prop.node, 'readonly ') | ||
)); | ||
} | ||
} | ||
}); | ||
}); | ||
}); | ||
}, | ||
})), | ||
}, | ||
}; | ||
}), | ||
}; |
@@ -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 @@ /** |
@@ -117,2 +117,3 @@ /** | ||
callbacksLast, | ||
noSortAlphabetically, | ||
sortShapeProp | ||
@@ -119,0 +120,0 @@ ); |
@@ -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'); | ||
@@ -271,13 +273,11 @@ const variableUtil = require('./variable'); | ||
/** @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; | ||
}, | ||
])); | ||
} | ||
@@ -284,0 +284,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]; |
@@ -72,5 +72,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)); | ||
@@ -97,15 +98,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 | ||
/** | ||
@@ -120,7 +125,16 @@ * 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. | ||
*/ | ||
const commentnodeMap = new WeakMap(); // all nodes reference WeakMap for start and end range | ||
function fixPropTypesSort(fixer, context, declarations, ignoreCase, requiredFirst, callbacksLast, sortShapeProp) { | ||
function fixPropTypesSort( | ||
fixer, | ||
context, | ||
declarations, | ||
ignoreCase, | ||
requiredFirst, | ||
callbacksLast, | ||
noSortAlphabetically, | ||
sortShapeProp | ||
) { | ||
function sortInSource(allNodes, source) { | ||
@@ -167,3 +181,3 @@ const originalSource = source; | ||
nodes, | ||
(a, b) => sorter(a, b, context, ignoreCase, requiredFirst, callbacksLast) | ||
(a, b) => sorter(a, b, context, ignoreCase, requiredFirst, callbacksLast, noSortAlphabetically) | ||
); | ||
@@ -174,6 +188,4 @@ | ||
const sourceCodeText = sourceCode.getText(); | ||
let sortedAttrText = sourceCodeText.substring( | ||
commentnodeMap.get(sortedAttr).start, | ||
commentnodeMap.get(sortedAttr).end | ||
); | ||
const commentNode = commentnodeMap.get(sortedAttr); | ||
let sortedAttrText = sourceCodeText.slice(commentNode.start, commentNode.end); | ||
if (sortShapeProp && isShapeProp(sortedAttr.value)) { | ||
@@ -180,0 +192,0 @@ const shape = getShapeProperties(sortedAttr.value); |
@@ -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 @@ } |
@@ -537,3 +537,3 @@ /** | ||
components.set(component ? component.node : node, { | ||
ignoreUnusedPropTypesValidation: true, | ||
ignoreUnusedPropTypesValidation: node.argument.type !== 'ObjectExpression', | ||
}); | ||
@@ -540,0 +540,0 @@ }, |
@@ -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.32.2", | ||
"version": "7.33.2", | ||
"author": "Yannick Croissant <yannick.croissant+npm@gmail.com>", | ||
@@ -32,2 +32,3 @@ "description": "React specific linting rules for ESLint", | ||
"doctrine": "^2.1.0", | ||
"es-iterator-helpers": "^1.0.12", | ||
"estraverse": "^5.3.0", | ||
@@ -42,12 +43,12 @@ "jsx-ast-utils": "^2.4.1 || ^3.0.0", | ||
"resolve": "^2.0.0-next.4", | ||
"semver": "^6.3.0", | ||
"semver": "^6.3.1", | ||
"string.prototype.matchall": "^4.0.8" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.20.12", | ||
"@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 +58,16 @@ "@types/estree": "0.0.52", | ||
"@typescript-eslint/parser": "^2.34.0 || ^3.10.1 || ^4.0.0 || ^5.0.0", | ||
"aud": "^2.0.2", | ||
"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.2", | ||
"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": "^1.0.0", | ||
"eslint-remote-tester-repositories": "^1.0.1", | ||
"eslint-scope": "^3.7.3", | ||
"espree": "^3.5.4", | ||
"istanbul": "^0.4.5", | ||
"ls-engines": "^0.8.0", | ||
"jackspeak": "=2.1.1", | ||
"ls-engines": "^0.8.1", | ||
"markdownlint-cli": "^0.8.0 || ^0.32.2", | ||
@@ -72,0 +74,0 @@ "mocha": "^5.2.0", |
@@ -20,3 +20,3 @@ # `eslint-plugin-react` <sup>[![Version Badge][npm-version-svg]][package-url]</sup> | ||
## Configuration (legacy: `.eslintrc*`) | ||
## Configuration (legacy: `.eslintrc*`) <a id="configuration"></a> | ||
@@ -248,3 +248,3 @@ Use [our preset](#recommended) to get reasonable defaults: | ||
...globals.serviceworker, | ||
...globals.browser; | ||
...globals.browser, | ||
}, | ||
@@ -251,0 +251,0 @@ }, |
794972
136
21403
17
30
+ Addedes-iterator-helpers@^1.0.12
+ Addedes-iterator-helpers@1.0.19(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.2(transitive)
+ Addedreflect.getprototypeof@1.0.6(transitive)
+ Addedwhich-builtin-type@1.1.3(transitive)
+ Addedwhich-collection@1.0.2(transitive)
Updatedsemver@^6.3.1