Skip to content

feat(eslint-plugin): [no-unnecessary-type-parameters] add suggestion fixer #10149

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

Merged
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
94 changes: 92 additions & 2 deletions packages/eslint-plugin/src/rules/no-unnecessary-type-parameters.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import type { Reference } from '@typescript-eslint/scope-manager';
import type { TSESTree } from '@typescript-eslint/utils';
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';

import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import * as tsutils from 'ts-api-utils';
import * as ts from 'typescript';

import type { MakeRequired } from '../util';

import { createRule, getParserServices, nullThrows } from '../util';
import {
createRule,
getParserServices,
nullThrows,
NullThrowsReasons,
} from '../util';

type NodeWithTypeParameters = MakeRequired<
ts.ClassLikeDeclaration | ts.SignatureDeclaration,
Expand All @@ -23,7 +28,10 @@ export default createRule({
recommended: 'strict',
requiresTypeChecking: true,
},
hasSuggestions: true,
messages: {
replaceUsagesWithConstraint:
'Replace all usages of type parameter with its constraint.',
sole: 'Type parameter {{name}} is {{uses}} in the {{descriptor}} signature.',
},
schema: [],
Expand Down Expand Up @@ -84,6 +92,88 @@ export default createRule({
descriptor,
uses: identifierCounts === 1 ? 'never used' : 'used only once',
},
suggest: [
{
messageId: 'replaceUsagesWithConstraint',
*fix(fixer): Generator<TSESLint.RuleFix> {
// Replace all the usages of the type parameter with the constraint...

const constraint = esTypeParameter.constraint;
// special case - a constraint of 'any' actually acts like 'unknown'
const constraintText =
constraint != null &&
constraint.type !== AST_NODE_TYPES.TSAnyKeyword
? context.sourceCode.getText(constraint)
: 'unknown';
for (const reference of smTypeParameterVariable.references) {
if (reference.isTypeReference) {
const referenceNode = reference.identifier;
yield fixer.replaceText(referenceNode, constraintText);
}
}

// ...and remove the type parameter itself from the declaration.

const typeParamsNode = nullThrows(
node.typeParameters,
'node should have type parameters',
);

// We are assuming at this point that the reported type parameter
// is present in the inspected node's type parameters.
if (typeParamsNode.params.length === 1) {
// Remove the whole <T> generic syntax if we're removing the only type parameter in the list.
yield fixer.remove(typeParamsNode);
} else {
const index = typeParamsNode.params.indexOf(esTypeParameter);

if (index === 0) {
const commaAfter = nullThrows(
context.sourceCode.getTokenAfter(
esTypeParameter,
token => token.value === ',',
),
NullThrowsReasons.MissingToken(
'comma',
'type parameter list',
),
);

const tokenAfterComma = nullThrows(
context.sourceCode.getTokenAfter(commaAfter, {
includeComments: true,
}),
NullThrowsReasons.MissingToken(
'token',
'type parameter list',
),
);

yield fixer.removeRange([
esTypeParameter.range[0],
tokenAfterComma.range[0],
]);
} else {
const commaBefore = nullThrows(
context.sourceCode.getTokenBefore(
esTypeParameter,
token => token.value === ',',
),
NullThrowsReasons.MissingToken(
'comma',
'type parameter list',
),
);

yield fixer.removeRange([
commaBefore.range[0],
esTypeParameter.range[1],
]);
}
}
},
},
],
});
}
}
Expand Down
Loading
Loading