diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b69232437..5ff0e08075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,14 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`jsx-key`]: detect conditional returns ([#3630][] @yialo) * [`jsx-newline`]: prevent a crash when `allowMultilines ([#3633][] @ljharb) +### Changed +* [Refactor] `propTypes`: extract type params to var ([#3634][] @HenryBrown0) +* [Refactor] [`boolean-prop-naming`]: invert if statement ([#3634][] @HenryBrown0) +* [Refactor] [`function-component-definition`]: exit early if no type params ([#3634][] @HenryBrown0) +* [Refactor] [`jsx-props-no-multi-spaces`]: extract type parameters to var ([#3634][] @HenryBrown0) + [#3638]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3638 +[#3634]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3634 [#3633]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3633 [#3630]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3630 [#3623]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3623 diff --git a/lib/rules/boolean-prop-naming.js b/lib/rules/boolean-prop-naming.js index f1c7420a7f..b5dd37b529 100644 --- a/lib/rules/boolean-prop-naming.js +++ b/lib/rules/boolean-prop-naming.js @@ -240,19 +240,24 @@ module.exports = { } if ( - component.node.parent - && component.node.parent.type === 'VariableDeclarator' - && component.node.parent.id - && component.node.parent.id.type === 'Identifier' - && component.node.parent.id.typeAnnotation - && component.node.parent.id.typeAnnotation.typeAnnotation - && component.node.parent.id.typeAnnotation.typeAnnotation.typeParameters - && ( - component.node.parent.id.typeAnnotation.typeAnnotation.typeParameters.type === 'TSTypeParameterInstantiation' - || component.node.parent.id.typeAnnotation.typeAnnotation.typeParameters.type === 'TypeParameterInstantiation' + !component.node.parent + || component.node.parent.type !== 'VariableDeclarator' + || !component.node.parent.id + || component.node.parent.id.type !== 'Identifier' + || !component.node.parent.id.typeAnnotation + || !component.node.parent.id.typeAnnotation.typeAnnotation + ) { + return; + } + + const annotationTypeParams = component.node.parent.id.typeAnnotation.typeAnnotation.typeParameters; + if ( + annotationTypeParams && ( + annotationTypeParams.type === 'TSTypeParameterInstantiation' + || annotationTypeParams.type === 'TypeParameterInstantiation' ) ) { - return component.node.parent.id.typeAnnotation.typeAnnotation.typeParameters.params.find( + return annotationTypeParams.params.find( (param) => param.type === 'TSTypeReference' || param.type === 'GenericTypeAnnotation' ); } diff --git a/lib/rules/function-component-definition.js b/lib/rules/function-component-definition.js index e0a27dedca..f8a95a9da0 100644 --- a/lib/rules/function-component-definition.js +++ b/lib/rules/function-component-definition.js @@ -33,14 +33,12 @@ const UNNAMED_FUNCTION_TEMPLATES = { }; function hasOneUnconstrainedTypeParam(node) { - if (node.typeParameters) { - return ( - node.typeParameters.params.length === 1 - && !node.typeParameters.params[0].constraint - ); - } + const nodeTypeParams = node.typeParameters; - return false; + return nodeTypeParams + && nodeTypeParams.params + && nodeTypeParams.params.length === 1 + && !nodeTypeParams.params[0].constraint; } function hasName(node) { diff --git a/lib/rules/jsx-props-no-multi-spaces.js b/lib/rules/jsx-props-no-multi-spaces.js index b0d34d59dd..b9cabafe32 100644 --- a/lib/rules/jsx-props-no-multi-spaces.js +++ b/lib/rules/jsx-props-no-multi-spaces.js @@ -98,8 +98,12 @@ module.exports = { } function containsGenericType(node) { - const containsTypeParams = typeof node.typeParameters !== 'undefined'; - return containsTypeParams && node.typeParameters.type === 'TSTypeParameterInstantiation'; + const nodeTypeParams = node.typeParameters; + if (typeof nodeTypeParams === 'undefined') { + return false; + } + + return nodeTypeParams.type === 'TSTypeParameterInstantiation'; } function getGenericNode(node) { diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js index c5aa35eb61..a189f871ca 100644 --- a/lib/util/propTypes.js +++ b/lib/util/propTypes.js @@ -627,7 +627,8 @@ module.exports = function propTypesInstructions(context, components, utils) { typeName = node.typeName.name; const leftMostName = getLeftMostTypeName(node.typeName); const shouldTraverseTypeParams = genericReactTypesImport.has(leftMostName); - if (shouldTraverseTypeParams && node.typeParameters && node.typeParameters.length !== 0) { + const nodeTypeParams = node.typeParameters; + if (shouldTraverseTypeParams && nodeTypeParams && nodeTypeParams.length !== 0) { // All react Generic types are derived from: // type PropsWithChildren

= P & { children?: ReactNode | undefined } // So we should construct an optional children prop @@ -638,7 +639,7 @@ module.exports = function propTypesInstructions(context, components, utils) { const idx = genericTypeParamIndexWherePropsArePresent[ leftMostName !== rightMostName ? rightMostName : importedName ]; - const nextNode = node.typeParameters.params[idx]; + const nextNode = nodeTypeParams.params[idx]; this.visitTSNode(nextNode); return; } @@ -727,9 +728,10 @@ module.exports = function propTypesInstructions(context, components, utils) { convertReturnTypeToPropTypes(node) { // ReturnType should always have one parameter - if (node.typeParameters) { - if (node.typeParameters.params.length === 1) { - let returnType = node.typeParameters.params[0]; + const nodeTypeParams = node.typeParameters; + if (nodeTypeParams) { + if (nodeTypeParams.params.length === 1) { + let returnType = nodeTypeParams.params[0]; // This line is trying to handle typescript-eslint-parser // typescript-eslint-parser TSTypeQuery is wrapped by TSTypeReference if (astUtil.isTSTypeReference(returnType)) { @@ -761,8 +763,9 @@ module.exports = function propTypesInstructions(context, components, utils) { case 'ObjectExpression': iterateProperties(context, res.properties, (key, value, propNode) => { if (propNode && propNode.argument && propNode.argument.type === 'CallExpression') { - if (propNode.argument.typeParameters) { - this.visitTSNode(propNode.argument.typeParameters); + const propNodeTypeParams = propNode.argument.typeParameters; + if (propNodeTypeParams) { + this.visitTSNode(propNodeTypeParams); } else { // Ignore this CallExpression return value since it doesn't have any typeParameters to let us know it's types. this.shouldIgnorePropTypes = true; @@ -960,8 +963,9 @@ module.exports = function propTypesInstructions(context, components, utils) { break; case 'GenericTypeAnnotation': if (propTypes.id.name === '$ReadOnly') { + const propTypeParams = propTypes.typeParameters; ignorePropsValidation = declarePropTypesForObjectTypeAnnotation( - propTypes.typeParameters.params[0], + propTypeParams.params[0], declaredPropTypes ); } else { @@ -1011,9 +1015,9 @@ module.exports = function propTypesInstructions(context, components, utils) { ) ) ) { - const propTypes = node.parent.typeParameters.params[1]; + const propTypesParams = node.parent.typeParameters; const declaredPropTypes = {}; - const obj = new DeclarePropTypesForTSTypeAnnotation(propTypes, declaredPropTypes); + const obj = new DeclarePropTypesForTSTypeAnnotation(propTypesParams.params[1], declaredPropTypes); components.set(node, { declaredPropTypes: obj.declaredPropTypes, ignorePropsValidation: obj.shouldIgnorePropTypes,