Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix] @typescript-eslint v6, v7, v8 use typeArguments with fallback to typeParameters #3629

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 60 additions & 2 deletions .github/workflows/node-18+.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,64 @@ jobs:
- 10
- 9
- 8
typescript-eslint:
- 5
- 6
- 7
- 8
exclude:
- eslint: 3
babel-eslint: 10
- eslint: 3
typescript-eslint: 5
- eslint: 4
typescript-eslint: 5
- eslint: 4.14
typescript-eslint: 5
- eslint: 5
typescript-eslint: 5
- eslint: 3
typescript-eslint: 6
- eslint: 4
typescript-eslint: 6
- eslint: 4.14
typescript-eslint: 6
- eslint: 5
typescript-eslint: 6
- eslint: 6
typescript-eslint: 6
- eslint: 9
typescript-eslint: 6
- eslint: 3
typescript-eslint: 7
- eslint: 4
typescript-eslint: 7
- eslint: 4.14
typescript-eslint: 7
- eslint: 5
typescript-eslint: 7
- eslint: 6
typescript-eslint: 7
- eslint: 7
typescript-eslint: 7
- eslint: 9
typescript-eslint: 7
- eslint: 3
typescript-eslint: 8
- eslint: 4
typescript-eslint: 8
- eslint: 4.14
typescript-eslint: 8
- eslint: 5
typescript-eslint: 8
- eslint: 6
typescript-eslint: 8
- eslint: 7
typescript-eslint: 8
- eslint: 9
typescript-eslint: 5
- node-version: 19
typescript-eslint: 7

steps:
- uses: actions/checkout@v4
Expand All @@ -46,9 +104,9 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
after_install: |
npm install --no-save "eslint@${{ matrix.eslint }}" "@typescript-eslint/parser@5" "babel-eslint@${{ matrix.babel-eslint }}"
npm install --no-save "eslint@${{ matrix.eslint }}" "@typescript-eslint/parser@${{ matrix.typescript-eslint }}" "babel-eslint@${{ matrix.babel-eslint }}"
env:
NPM_CONFIG_LEGACY_PEER_DEPS: true
NPM_CONFIG_LEGACY_PEER_DEPS: "${{ matrix.typescript-eslint >= 6 && 'false' || 'true' }}"
- run: npx ls-engines
- run: npm run unit-test
- uses: codecov/codecov-action@v3.1.5
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/node-minors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,10 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
after_install: |
npm install --no-save "eslint@${{ matrix.eslint }}" "@typescript-eslint/parser@${{ matrix.node-version >= 14 && '5' || (matrix.node-version >= 12 && '4' || (matrix.node-version >= 10 && '4.0' || (matrix.node-version >= 8 && '3' || '2'))) }}" "babel-eslint@${{ matrix.babel-eslint }}"
npm install --no-save "eslint@${{ matrix.eslint }}" "@typescript-eslint/parser@${{ matrix.node-version >= 18 && matrix.eslint >= 8 && '8' || (matrix.node-version >= 16 && matrix.eslint >= 7 && '6' || (matrix.node-version >= 14 && '5' || (matrix.node-version >= 12 && '4' || (matrix.node-version >= 10 && '4.0' || (matrix.node-version >= 8 && '3' || '2'))))) }}" "babel-eslint@${{ matrix.babel-eslint }}"
skip-ls-check: ${{ matrix.node-version < 10 && true || false }}
env:
NPM_CONFIG_LEGACY_PEER_DEPS: true
NPM_CONFIG_LEGACY_PEER_DEPS: "${{ matrix.node-version >= 16 && matrix.eslint >= 7 && 'false' || 'true' }}"
- run: npx ls-engines
if: ${{ matrix.node-version >= 12 }}
- run: npm run unit-test
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Change Log

All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/).
This change log adheres to standards from [Keep a CHANGELOG](https://keepachangelog.com).
Expand All @@ -8,6 +9,14 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
### Added
* [`no-string-refs`]: allow this.refs in > 18.3.0 ([#3807][] @henryqdineen)

### Fixed
* [`function-component-definition`], [`boolean-prop-naming`], [`jsx-first-prop-new-line`], [`jsx-props-no-multi-spaces`], `propTypes`: use type args ([#3629][] @HenryBrown0)

### Changed
* [Tests] add @typescript-eslint/parser v6 ([#3629][] @HenryBrown0)
* [Tests] add @typescript-eslint/parser v7 and v8 ([#3629][] @hampustagerud)

[#3629]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3629
[#3807]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3807

## [7.35.2] - 2024.09.03
Expand Down
12 changes: 7 additions & 5 deletions lib/rules/boolean-prop-naming.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,14 +259,16 @@ module.exports = {
return;
}

const annotationTypeParams = component.node.parent.id.typeAnnotation.typeAnnotation.typeParameters;
const annotationTypeArguments = propsUtil.getTypeArguments(
component.node.parent.id.typeAnnotation.typeAnnotation
);
if (
annotationTypeParams && (
annotationTypeParams.type === 'TSTypeParameterInstantiation'
|| annotationTypeParams.type === 'TypeParameterInstantiation'
annotationTypeArguments && (
annotationTypeArguments.type === 'TSTypeParameterInstantiation'
|| annotationTypeArguments.type === 'TypeParameterInstantiation'
)
) {
return annotationTypeParams.params.find(
return annotationTypeArguments.params.find(
(param) => param.type === 'TSTypeReference' || param.type === 'GenericTypeAnnotation'
);
}
Expand Down
14 changes: 8 additions & 6 deletions lib/rules/function-component-definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const Components = require('../util/Components');
const docsUrl = require('../util/docsUrl');
const reportC = require('../util/report');
const getText = require('../util/eslint').getText;
const propsUtil = require('../util/props');

// ------------------------------------------------------------------------------
// Rule Definition
Expand All @@ -34,12 +35,12 @@ const UNNAMED_FUNCTION_TEMPLATES = {
};

function hasOneUnconstrainedTypeParam(node) {
const nodeTypeParams = node.typeParameters;
const nodeTypeArguments = propsUtil.getTypeArguments(node);

return nodeTypeParams
&& nodeTypeParams.params
&& nodeTypeParams.params.length === 1
&& !nodeTypeParams.params[0].constraint;
return nodeTypeArguments
&& nodeTypeArguments.params
&& nodeTypeArguments.params.length === 1
&& !nodeTypeArguments.params[0].constraint;
}

function hasName(node) {
Expand Down Expand Up @@ -202,11 +203,12 @@ module.exports = {
varType = node.parent.parent.kind;
}

const nodeTypeArguments = propsUtil.getTypeArguments(node);
return (fixer) => fixer.replaceTextRange(
options.range,
buildFunction(options.template, {
typeAnnotation,
typeParams: getNodeText(node.typeParameters, source),
typeParams: getNodeText(nodeTypeArguments, source),
params: getParams(node, source),
returnType: getNodeText(node.returnType, source),
body: getBody(node, source),
Expand Down
4 changes: 3 additions & 1 deletion lib/rules/jsx-first-prop-new-line.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

const docsUrl = require('../util/docsUrl');
const report = require('../util/report');
const propsUtil = require('../util/props');

// ------------------------------------------------------------------------------
// Rule Definition
Expand Down Expand Up @@ -55,7 +56,8 @@ module.exports = {
report(context, messages.propOnNewLine, 'propOnNewLine', {
node: decl,
fix(fixer) {
return fixer.replaceTextRange([(node.typeParameters || node.name).range[1], decl.range[0]], '\n');
const nodeTypeArguments = propsUtil.getTypeArguments(node);
return fixer.replaceTextRange([(nodeTypeArguments || node.name).range[1], decl.range[0]], '\n');
},
});
}
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/jsx-max-depth.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ module.exports = {
function findJSXElementOrFragment(startNode, name, previousReferences) {
function find(refs, prevRefs) {
for (let i = refs.length - 1; i >= 0; i--) {
if (has(refs[i], 'writeExpr')) {
if (typeof refs[i].writeExpr !== 'undefined') {
const writeExpr = refs[i].writeExpr;

return (jsxUtil.isJSX(writeExpr)
Expand Down
11 changes: 6 additions & 5 deletions lib/rules/jsx-props-no-multi-spaces.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
const docsUrl = require('../util/docsUrl');
const eslintUtil = require('../util/eslint');
const report = require('../util/report');
const propsUtil = require('../util/props');

const getSourceCode = eslintUtil.getSourceCode;
const getText = eslintUtil.getText;
Expand Down Expand Up @@ -103,26 +104,26 @@ module.exports = {
}

function containsGenericType(node) {
const nodeTypeParams = node.typeParameters;
if (typeof nodeTypeParams === 'undefined') {
const nodeTypeArguments = propsUtil.getTypeArguments(node);
if (typeof nodeTypeArguments === 'undefined') {
return false;
}

return nodeTypeParams.type === 'TSTypeParameterInstantiation';
return nodeTypeArguments.type === 'TSTypeParameterInstantiation';
}

function getGenericNode(node) {
const name = node.name;
if (containsGenericType(node)) {
const type = node.typeParameters;
const nodeTypeArguments = propsUtil.getTypeArguments(node);

return Object.assign(
{},
node,
{
range: [
name.range[0],
type.range[1],
nodeTypeArguments.range[1],
],
}
);
Expand Down
53 changes: 30 additions & 23 deletions lib/util/propTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ function isFunctionType(node) {
*/
function isSuperTypeParameterPropsDeclaration(node) {
if (node && (node.type === 'ClassDeclaration' || node.type === 'ClassExpression')) {
if (node.superTypeParameters && node.superTypeParameters.params.length > 0) {
const parameters = propsUtil.getSuperTypeArguments(node);
if (parameters && parameters.params.length > 0) {
return true;
}
}
Expand Down Expand Up @@ -639,8 +640,8 @@ module.exports = function propTypesInstructions(context, components, utils) {
typeName = node.typeName.name;
const leftMostName = getLeftMostTypeName(node.typeName);
const shouldTraverseTypeParams = genericReactTypesImport.has(leftMostName);
const nodeTypeParams = node.typeParameters;
if (shouldTraverseTypeParams && nodeTypeParams && nodeTypeParams.length !== 0) {
const nodeTypeArguments = propsUtil.getTypeArguments(node);
if (shouldTraverseTypeParams && nodeTypeArguments && nodeTypeArguments.length !== 0) {
// All react Generic types are derived from:
// type PropsWithChildren<P> = P & { children?: ReactNode | undefined }
// So we should construct an optional children prop
Expand All @@ -662,7 +663,7 @@ module.exports = function propTypesInstructions(context, components, utils) {
const idx = genericTypeParamIndexWherePropsArePresent[
leftMostName !== rightMostName ? rightMostName : importedName
];
const nextNode = nodeTypeParams.params[idx];
const nextNode = nodeTypeArguments.params[idx];
this.visitTSNode(nextNode);
return;
}
Expand Down Expand Up @@ -759,10 +760,10 @@ module.exports = function propTypesInstructions(context, components, utils) {

convertReturnTypeToPropTypes(node, rootNode) {
// ReturnType<T> should always have one parameter
const nodeTypeParams = node.typeParameters;
if (nodeTypeParams) {
if (nodeTypeParams.params.length === 1) {
let returnType = nodeTypeParams.params[0];
const nodeTypeArguments = propsUtil.getTypeArguments(node);
if (nodeTypeArguments) {
if (nodeTypeArguments.params.length === 1) {
let returnType = nodeTypeArguments.params[0];
// This line is trying to handle typescript-eslint-parser
// typescript-eslint-parser TSTypeQuery is wrapped by TSTypeReference
if (astUtil.isTSTypeReference(returnType)) {
Expand Down Expand Up @@ -794,9 +795,9 @@ module.exports = function propTypesInstructions(context, components, utils) {
case 'ObjectExpression':
iterateProperties(context, res.properties, (key, value, propNode) => {
if (propNode && astUtil.isCallExpression(propNode.argument)) {
const propNodeTypeParams = propNode.argument.typeParameters;
if (propNodeTypeParams) {
this.visitTSNode(propNodeTypeParams);
const propNodeTypeArguments = propsUtil.getTypeArguments(propNode.argument);
if (propNodeTypeArguments) {
this.visitTSNode(propNodeTypeArguments);
} else {
// Ignore this CallExpression return value since it doesn't have any typeParameters to let us know it's types.
this.shouldIgnorePropTypes = true;
Expand All @@ -816,8 +817,8 @@ module.exports = function propTypesInstructions(context, components, utils) {
});
break;
case 'CallExpression':
if (res.typeParameters) {
this.visitTSNode(res.typeParameters);
if (propsUtil.getTypeArguments(res)) {
this.visitTSNode(propsUtil.getTypeArguments(res));
} else {
// Ignore this CallExpression return value since it doesn't have any typeParameters to let us know it's types.
this.shouldIgnorePropTypes = true;
Expand Down Expand Up @@ -1002,9 +1003,9 @@ module.exports = function propTypesInstructions(context, components, utils) {
break;
case 'GenericTypeAnnotation':
if (propTypes.id.name === '$ReadOnly') {
const propTypeParams = propTypes.typeParameters;
const propTypeArguments = propsUtil.getTypeArguments(propTypes);
ignorePropsValidation = declarePropTypesForObjectTypeAnnotation(
propTypeParams.params[0],
propTypeArguments.params[0],
declaredPropTypes
);
} else {
Expand Down Expand Up @@ -1041,11 +1042,16 @@ module.exports = function propTypesInstructions(context, components, utils) {
return;
}

let propTypesArguments = null;
if (node.parent) {
propTypesArguments = propsUtil.getTypeArguments(node.parent);
}

if (
node.parent
&& node.parent.callee
&& node.parent.typeParameters
&& node.parent.typeParameters.params
&& propTypesArguments
&& propTypesArguments.params
&& (
node.parent.callee.name === 'forwardRef' || (
node.parent.callee.object
Expand All @@ -1055,9 +1061,8 @@ module.exports = function propTypesInstructions(context, components, utils) {
)
)
) {
const propTypesParams = node.parent.typeParameters;
const declaredPropTypes = {};
const obj = new DeclarePropTypesForTSTypeAnnotation(propTypesParams.params[1], declaredPropTypes, rootNode);
const obj = new DeclarePropTypesForTSTypeAnnotation(propTypesArguments.params[1], declaredPropTypes, rootNode);
components.set(node, {
declaredPropTypes: obj.declaredPropTypes,
ignorePropsValidation: obj.shouldIgnorePropTypes,
Expand Down Expand Up @@ -1103,7 +1108,7 @@ module.exports = function propTypesInstructions(context, components, utils) {
if (
annotation
&& annotation.type !== 'TSTypeReference'
&& annotation.typeParameters == null
&& propsUtil.getTypeArguments(annotation) == null
) {
return;
}
Expand All @@ -1115,23 +1120,25 @@ module.exports = function propTypesInstructions(context, components, utils) {
}

/**
* Resolve the type annotation for a given class declaration node with superTypeParameters.
* Resolve the type annotation for a given class declaration node.
*
* @param {ASTNode} node The annotation or a node containing the type annotation.
* @returns {ASTNode} The resolved type annotation for the node.
*/
function resolveSuperParameterPropsType(node) {
let propsParameterPosition;
const parameters = propsUtil.getSuperTypeArguments(node);

try {
// Flow <=0.52 had 3 required TypedParameters of which the second one is the Props.
// Flow >=0.53 has 2 optional TypedParameters of which the first one is the Props.
propsParameterPosition = testFlowVersion(context, '>= 0.53.0') ? 0 : 1;
} catch (e) {
// In case there is no flow version defined, we can safely assume that when there are 3 Props we are dealing with version <= 0.52
propsParameterPosition = node.superTypeParameters.params.length <= 2 ? 0 : 1;
propsParameterPosition = parameters.params.length <= 2 ? 0 : 1;
}

let annotation = node.superTypeParameters.params[propsParameterPosition];
let annotation = parameters.params[propsParameterPosition];
while (annotation && (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation')) {
annotation = annotation.typeAnnotation;
}
Expand Down
Loading