diff --git a/eslint.config.mjs b/eslint.config.mjs
index a5abacc22809..88929e3bb2b4 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -148,6 +148,7 @@ export default tseslint.config(
'error',
{ allowConstantLoopConditions: true, checkTypePredicates: true },
],
+ '@typescript-eslint/no-unnecessary-type-conversion': 'error',
'@typescript-eslint/no-unnecessary-type-parameters': 'error',
'@typescript-eslint/no-unused-expressions': 'error',
'@typescript-eslint/no-unused-vars': [
diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-type-conversion.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-type-conversion.mdx
new file mode 100644
index 000000000000..01a69bd90031
--- /dev/null
+++ b/packages/eslint-plugin/docs/rules/no-unnecessary-type-conversion.mdx
@@ -0,0 +1,74 @@
+---
+description: 'Disallow conversion idioms when they do not change the type or value of the expression.'
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+> 🛑 This file is source code, not the primary documentation location! 🛑
+>
+> See **https://typescript-eslint.io/rules/no-unnecessary-type-conversion** for documentation.
+
+JavaScript has several idioms used to convert values to certain types. With TypeScript, it is possible to see at build time if the value is already of that type, making the conversion unnecessary.
+Performing unnecessary conversions increases visual clutter and harms code readability, so it's generally best practice to remove them if they don't change the type of an expression.
+This rule reports when a type conversion idiom is identified which does not change the runtime type of an expression.
+
+## Examples
+
+
+
+
+```ts
+String('123');
+'123'.toString();
+'' + '123';
+'123' + '';
+
+Number(123);
++123;
+~~123;
+
+Boolean(true);
+!!true;
+
+BigInt(BigInt(1));
+
+let str = '123';
+str += '';
+```
+
+
+
+
+```ts
+function foo(bar: string | number) {
+ String(bar);
+ bar.toString();
+ '' + bar;
+ bar + '';
+
+ Number(bar);
+ +bar;
+ ~~bar;
+
+ Boolean(bar);
+ !!bar;
+
+ BigInt(1);
+
+ bar += '';
+}
+```
+
+
+
+
+## When Not To Use It
+
+If you don't care about having no-op type conversions in your code, then you can turn off this rule.
+If you have types which are not accurate, then this rule might cause you to remove conversions that you actually do need.
+
+## Related To
+
+- [no-unnecessary-type-assertion](./no-unnecessary-type-assertion.mdx)
+- [no-useless-template-literals](./no-useless-template-literals.mdx)
diff --git a/packages/eslint-plugin/src/configs/eslintrc/all.ts b/packages/eslint-plugin/src/configs/eslintrc/all.ts
index 4a5b15ea9f43..3d25e79e33db 100644
--- a/packages/eslint-plugin/src/configs/eslintrc/all.ts
+++ b/packages/eslint-plugin/src/configs/eslintrc/all.ts
@@ -98,6 +98,7 @@ export = {
'@typescript-eslint/no-unnecessary-type-arguments': 'error',
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
'@typescript-eslint/no-unnecessary-type-constraint': 'error',
+ '@typescript-eslint/no-unnecessary-type-conversion': 'error',
'@typescript-eslint/no-unnecessary-type-parameters': 'error',
'@typescript-eslint/no-unsafe-argument': 'error',
'@typescript-eslint/no-unsafe-assignment': 'error',
diff --git a/packages/eslint-plugin/src/configs/eslintrc/disable-type-checked.ts b/packages/eslint-plugin/src/configs/eslintrc/disable-type-checked.ts
index 2229d6715172..9dd6c95c929e 100644
--- a/packages/eslint-plugin/src/configs/eslintrc/disable-type-checked.ts
+++ b/packages/eslint-plugin/src/configs/eslintrc/disable-type-checked.ts
@@ -34,6 +34,7 @@ export = {
'@typescript-eslint/no-unnecessary-template-expression': 'off',
'@typescript-eslint/no-unnecessary-type-arguments': 'off',
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
+ '@typescript-eslint/no-unnecessary-type-conversion': 'off',
'@typescript-eslint/no-unnecessary-type-parameters': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
diff --git a/packages/eslint-plugin/src/configs/flat/all.ts b/packages/eslint-plugin/src/configs/flat/all.ts
index 4bf1a3b4c5d4..dc40d391e4fd 100644
--- a/packages/eslint-plugin/src/configs/flat/all.ts
+++ b/packages/eslint-plugin/src/configs/flat/all.ts
@@ -112,6 +112,7 @@ export default (
'@typescript-eslint/no-unnecessary-type-arguments': 'error',
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
'@typescript-eslint/no-unnecessary-type-constraint': 'error',
+ '@typescript-eslint/no-unnecessary-type-conversion': 'error',
'@typescript-eslint/no-unnecessary-type-parameters': 'error',
'@typescript-eslint/no-unsafe-argument': 'error',
'@typescript-eslint/no-unsafe-assignment': 'error',
diff --git a/packages/eslint-plugin/src/configs/flat/disable-type-checked.ts b/packages/eslint-plugin/src/configs/flat/disable-type-checked.ts
index 838f00e62c29..15c5bb0e3dcf 100644
--- a/packages/eslint-plugin/src/configs/flat/disable-type-checked.ts
+++ b/packages/eslint-plugin/src/configs/flat/disable-type-checked.ts
@@ -41,6 +41,7 @@ export default (
'@typescript-eslint/no-unnecessary-template-expression': 'off',
'@typescript-eslint/no-unnecessary-type-arguments': 'off',
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
+ '@typescript-eslint/no-unnecessary-type-conversion': 'off',
'@typescript-eslint/no-unnecessary-type-parameters': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
diff --git a/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts b/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts
index 4729da05be7c..1c2b5ad1abaa 100644
--- a/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts
+++ b/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts
@@ -79,7 +79,7 @@ export default createRule({
return {
...getNameFromMember(member, context.sourceCode),
callSignature: false,
- static: !!member.static,
+ static: member.static,
};
case AST_NODE_TYPES.TSCallSignatureDeclaration:
return {
diff --git a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts
index a8ee4aa3766b..8f1edc721685 100644
--- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts
+++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts
@@ -169,14 +169,14 @@ export default createRule({
}
}
}
- if (!!funcName && !!options.allowedNames.includes(funcName)) {
+ if (!!funcName && options.allowedNames.includes(funcName)) {
return true;
}
}
if (
node.type === AST_NODE_TYPES.FunctionDeclaration &&
node.id &&
- !!options.allowedNames.includes(node.id.name)
+ options.allowedNames.includes(node.id.name)
) {
return true;
}
diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts
index f1587b5d2b1f..2a82c7e18c6b 100644
--- a/packages/eslint-plugin/src/rules/index.ts
+++ b/packages/eslint-plugin/src/rules/index.ts
@@ -75,6 +75,7 @@ import noUnnecessaryTemplateExpression from './no-unnecessary-template-expressio
import noUnnecessaryTypeArguments from './no-unnecessary-type-arguments';
import noUnnecessaryTypeAssertion from './no-unnecessary-type-assertion';
import noUnnecessaryTypeConstraint from './no-unnecessary-type-constraint';
+import noUnnecessaryTypeConversion from './no-unnecessary-type-conversion';
import noUnnecessaryTypeParameters from './no-unnecessary-type-parameters';
import noUnsafeArgument from './no-unsafe-argument';
import noUnsafeAssignment from './no-unsafe-assignment';
@@ -208,6 +209,7 @@ const rules = {
'no-unnecessary-type-arguments': noUnnecessaryTypeArguments,
'no-unnecessary-type-assertion': noUnnecessaryTypeAssertion,
'no-unnecessary-type-constraint': noUnnecessaryTypeConstraint,
+ 'no-unnecessary-type-conversion': noUnnecessaryTypeConversion,
'no-unnecessary-type-parameters': noUnnecessaryTypeParameters,
'no-unsafe-argument': noUnsafeArgument,
'no-unsafe-assignment': noUnsafeAssignment,
diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts
index 1f5dee70984c..7ae9ec7627cf 100644
--- a/packages/eslint-plugin/src/rules/member-ordering.ts
+++ b/packages/eslint-plugin/src/rules/member-ordering.ts
@@ -488,7 +488,7 @@ function isMemberOptional(node: Member): boolean {
case AST_NODE_TYPES.PropertyDefinition:
case AST_NODE_TYPES.TSAbstractMethodDefinition:
case AST_NODE_TYPES.MethodDefinition:
- return !!node.optional;
+ return node.optional;
}
return false;
}
diff --git a/packages/eslint-plugin/src/rules/no-duplicate-enum-values.ts b/packages/eslint-plugin/src/rules/no-duplicate-enum-values.ts
index 40a0ccee82b3..c5a858dbdcaa 100644
--- a/packages/eslint-plugin/src/rules/no-duplicate-enum-values.ts
+++ b/packages/eslint-plugin/src/rules/no-duplicate-enum-values.ts
@@ -58,9 +58,9 @@ export default createRule({
let value: number | string | undefined;
if (isStringLiteral(member.initializer)) {
- value = String(member.initializer.value);
+ value = member.initializer.value;
} else if (isNumberLiteral(member.initializer)) {
- value = Number(member.initializer.value);
+ value = member.initializer.value;
} else if (isStaticTemplateLiteral(member.initializer)) {
value = member.initializer.quasis[0].value.cooked;
}
diff --git a/packages/eslint-plugin/src/rules/no-empty-interface.ts b/packages/eslint-plugin/src/rules/no-empty-interface.ts
index cbcb38f1092c..4330f27e0acf 100644
--- a/packages/eslint-plugin/src/rules/no-empty-interface.ts
+++ b/packages/eslint-plugin/src/rules/no-empty-interface.ts
@@ -97,11 +97,10 @@ export default createRule({
def => def.node.type === AST_NODE_TYPES.ClassDeclaration,
);
- const isInAmbientDeclaration = !!(
+ const isInAmbientDeclaration =
isDefinitionFile(context.filename) &&
scope.type === ScopeType.tsModule &&
- scope.block.declare
- );
+ scope.block.declare;
const useAutoFix = !(
isInAmbientDeclaration || mergedWithClassDeclaration
diff --git a/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts b/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts
index b550802df58d..acf73ce9326a 100644
--- a/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts
+++ b/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts
@@ -172,7 +172,7 @@ function describeLiteralTypeNode(typeNode: TSESTree.TypeNode): string {
}
function isNodeInsideReturnType(node: TSESTree.TSUnionType): boolean {
- return !!(
+ return (
node.parent.type === AST_NODE_TYPES.TSTypeAnnotation &&
isFunctionOrFunctionType(node.parent.parent)
);
diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts
index 6c70e134043e..c056e1af1a07 100644
--- a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts
+++ b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts
@@ -35,9 +35,7 @@ const evenNumOfBackslashesRegExp = /(?({
@@ -316,10 +314,7 @@ export default createRule<[], MessageId>({
// \${ -> \${
// \\${ -> \\\${
.replaceAll(
- new RegExp(
- `${String(evenNumOfBackslashesRegExp.source)}(\`|\\\${)`,
- 'g',
- ),
+ new RegExp(`${evenNumOfBackslashesRegExp.source}(\`|\\\${)`, 'g'),
'\\$1',
);
diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-conversion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-conversion.ts
new file mode 100644
index 000000000000..6e5e5e5794e1
--- /dev/null
+++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-conversion.ts
@@ -0,0 +1,376 @@
+import type { TSESTree } from '@typescript-eslint/utils';
+import type { RuleFix, RuleFixer } from '@typescript-eslint/utils/ts-eslint';
+
+import { AST_NODE_TYPES } from '@typescript-eslint/utils';
+import { unionTypeParts } from 'ts-api-utils';
+import * as ts from 'typescript';
+
+import {
+ createRule,
+ getConstrainedTypeAtLocation,
+ getParserServices,
+ getWrappingFixer,
+ isTypeFlagSet,
+} from '../util';
+
+type Options = [];
+type MessageIds =
+ | 'suggestRemove'
+ | 'suggestSatisfies'
+ | 'unnecessaryTypeConversion';
+
+export default createRule({
+ name: 'no-unnecessary-type-conversion',
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description:
+ 'Disallow conversion idioms when they do not change the type or value of the expression',
+ requiresTypeChecking: true,
+ },
+ fixable: 'code',
+ hasSuggestions: true,
+ messages: {
+ suggestRemove: 'Remove the type conversion.',
+ suggestSatisfies:
+ 'Instead, assert that the value satisfies the {{type}} type.',
+ unnecessaryTypeConversion:
+ '{{violation}} does not change the type or value of the {{type}}.',
+ },
+ schema: [],
+ },
+ defaultOptions: [],
+ create(context) {
+ function doesUnderlyingTypeMatchFlag(
+ type: ts.Type,
+ typeFlag: ts.TypeFlags,
+ ): boolean {
+ return unionTypeParts(type).every(t => isTypeFlagSet(t, typeFlag));
+ }
+
+ const services = getParserServices(context);
+
+ function handleUnaryOperator(
+ node: TSESTree.UnaryExpression,
+ typeFlag: ts.TypeFlags,
+ typeString: 'boolean' | 'number',
+ violation: string,
+ isDoubleOperator: boolean, // !! or ~~
+ ) {
+ const outerNode = isDoubleOperator ? node.parent : node;
+ const type = services.getTypeAtLocation(node.argument);
+ if (doesUnderlyingTypeMatchFlag(type, typeFlag)) {
+ const wrappingFixerParams = {
+ node: outerNode,
+ innerNode: [node.argument],
+ sourceCode: context.sourceCode,
+ };
+
+ context.report({
+ loc: {
+ start: outerNode.loc.start,
+ end: {
+ column: node.loc.start.column + 1,
+ line: node.loc.start.line,
+ },
+ },
+ messageId: 'unnecessaryTypeConversion',
+ data: { type: typeString, violation },
+ suggest: [
+ {
+ messageId: 'suggestRemove',
+ fix: getWrappingFixer(wrappingFixerParams),
+ },
+ {
+ messageId: 'suggestSatisfies',
+ data: { type: typeString },
+ fix: getWrappingFixer({
+ ...wrappingFixerParams,
+ wrap: expr => `${expr} satisfies ${typeString}`,
+ }),
+ },
+ ],
+ });
+ }
+ }
+
+ return {
+ 'AssignmentExpression[operator = "+="]'(
+ node: TSESTree.AssignmentExpression,
+ ): void {
+ if (
+ node.right.type === AST_NODE_TYPES.Literal &&
+ node.right.value === '' &&
+ doesUnderlyingTypeMatchFlag(
+ services.getTypeAtLocation(node.left),
+ ts.TypeFlags.StringLike,
+ )
+ ) {
+ const wrappingFixerParams = {
+ node,
+ innerNode: [node.left],
+ sourceCode: context.sourceCode,
+ };
+
+ context.report({
+ node,
+ messageId: 'unnecessaryTypeConversion',
+ data: {
+ type: 'string',
+ violation: "Concatenating a string with ''",
+ },
+ suggest: [
+ {
+ messageId: 'suggestRemove',
+ fix:
+ node.parent.type === AST_NODE_TYPES.ExpressionStatement
+ ? (fixer: RuleFixer): RuleFix[] => [
+ fixer.removeRange([
+ node.parent.range[0],
+ node.parent.range[1],
+ ]),
+ ]
+ : getWrappingFixer(wrappingFixerParams),
+ },
+ {
+ messageId: 'suggestSatisfies',
+ data: { type: 'string' },
+ fix: getWrappingFixer({
+ ...wrappingFixerParams,
+ wrap: expr => `${expr} satisfies string`,
+ }),
+ },
+ ],
+ });
+ }
+ },
+ 'BinaryExpression[operator = "+"]'(
+ node: TSESTree.BinaryExpression,
+ ): void {
+ if (
+ node.right.type === AST_NODE_TYPES.Literal &&
+ node.right.value === '' &&
+ doesUnderlyingTypeMatchFlag(
+ services.getTypeAtLocation(node.left),
+ ts.TypeFlags.StringLike,
+ )
+ ) {
+ const wrappingFixerParams = {
+ node,
+ innerNode: [node.left],
+ sourceCode: context.sourceCode,
+ };
+
+ context.report({
+ loc: {
+ start: node.left.loc.end,
+ end: node.loc.end,
+ },
+ messageId: 'unnecessaryTypeConversion',
+ data: {
+ type: 'string',
+ violation: "Concatenating a string with ''",
+ },
+ suggest: [
+ {
+ messageId: 'suggestRemove',
+ fix: getWrappingFixer(wrappingFixerParams),
+ },
+ {
+ messageId: 'suggestSatisfies',
+ data: { type: 'string' },
+ fix: getWrappingFixer({
+ ...wrappingFixerParams,
+ wrap: expr => `${expr} satisfies string`,
+ }),
+ },
+ ],
+ });
+ } else if (
+ node.left.type === AST_NODE_TYPES.Literal &&
+ node.left.value === '' &&
+ doesUnderlyingTypeMatchFlag(
+ services.getTypeAtLocation(node.right),
+ ts.TypeFlags.StringLike,
+ )
+ ) {
+ const wrappingFixerParams = {
+ node,
+ innerNode: [node.right],
+ sourceCode: context.sourceCode,
+ };
+
+ context.report({
+ loc: {
+ start: node.loc.start,
+ end: node.right.loc.start,
+ },
+ messageId: 'unnecessaryTypeConversion',
+ data: {
+ type: 'string',
+ violation: "Concatenating '' with a string",
+ },
+ suggest: [
+ {
+ messageId: 'suggestRemove',
+ fix: getWrappingFixer(wrappingFixerParams),
+ },
+ {
+ messageId: 'suggestSatisfies',
+ data: { type: 'string' },
+ fix: getWrappingFixer({
+ ...wrappingFixerParams,
+ wrap: expr => `${expr} satisfies string`,
+ }),
+ },
+ ],
+ });
+ }
+ },
+ CallExpression(node: TSESTree.CallExpression): void {
+ const nodeCallee = node.callee;
+ if (
+ nodeCallee.type === AST_NODE_TYPES.Identifier &&
+ node.arguments.length === 1
+ ) {
+ const getTypeLazy = () =>
+ getConstrainedTypeAtLocation(services, node.arguments[0]);
+
+ const isBuiltInCall = (name: string) => {
+ if (nodeCallee.name === name) {
+ const scope = context.sourceCode.getScope(node);
+ const variable = scope.set.get(name);
+ return !variable?.defs.length;
+ }
+ return false;
+ };
+
+ if (
+ // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum
+ (isBuiltInCall('String') &&
+ doesUnderlyingTypeMatchFlag(
+ getTypeLazy(),
+ ts.TypeFlags.StringLike,
+ )) ||
+ (isBuiltInCall('Number') &&
+ doesUnderlyingTypeMatchFlag(
+ getTypeLazy(),
+ ts.TypeFlags.NumberLike,
+ )) ||
+ // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum
+ (isBuiltInCall('Boolean') &&
+ doesUnderlyingTypeMatchFlag(
+ getTypeLazy(),
+ ts.TypeFlags.BooleanLike,
+ )) ||
+ (isBuiltInCall('BigInt') &&
+ doesUnderlyingTypeMatchFlag(
+ getTypeLazy(),
+ ts.TypeFlags.BigIntLike,
+ ))
+ ) {
+ const wrappingFixerParams = {
+ node,
+ innerNode: [node.arguments[0]],
+ sourceCode: context.sourceCode,
+ };
+ const typeString = nodeCallee.name.toLowerCase();
+
+ context.report({
+ node: nodeCallee,
+ messageId: 'unnecessaryTypeConversion',
+ data: {
+ type: nodeCallee.name.toLowerCase(),
+ violation: `Passing a ${typeString} to ${nodeCallee.name}()`,
+ },
+ suggest: [
+ {
+ messageId: 'suggestRemove',
+ fix: getWrappingFixer(wrappingFixerParams),
+ },
+ {
+ messageId: 'suggestSatisfies',
+ data: { type: typeString },
+ fix: getWrappingFixer({
+ ...wrappingFixerParams,
+ wrap: expr => `${expr} satisfies ${typeString}`,
+ }),
+ },
+ ],
+ });
+ }
+ }
+ },
+ 'CallExpression > MemberExpression.callee > Identifier[name = "toString"].property'(
+ node: TSESTree.Expression,
+ ): void {
+ const memberExpr = node.parent as TSESTree.MemberExpression;
+ const type = getConstrainedTypeAtLocation(services, memberExpr.object);
+ if (doesUnderlyingTypeMatchFlag(type, ts.TypeFlags.StringLike)) {
+ const wrappingFixerParams = {
+ node: memberExpr.parent,
+ innerNode: [memberExpr.object],
+ sourceCode: context.sourceCode,
+ };
+
+ context.report({
+ loc: {
+ start: memberExpr.property.loc.start,
+ end: memberExpr.parent.loc.end,
+ },
+ messageId: 'unnecessaryTypeConversion',
+ data: {
+ type: 'string',
+ violation: "Calling a string's .toString() method",
+ },
+ suggest: [
+ {
+ messageId: 'suggestRemove',
+ fix: getWrappingFixer(wrappingFixerParams),
+ },
+ {
+ messageId: 'suggestSatisfies',
+ data: { type: 'string' },
+ fix: getWrappingFixer({
+ ...wrappingFixerParams,
+ wrap: expr => `${expr} satisfies string`,
+ }),
+ },
+ ],
+ });
+ }
+ },
+ 'UnaryExpression[operator = "!"] > UnaryExpression[operator = "!"]'(
+ node: TSESTree.UnaryExpression,
+ ): void {
+ handleUnaryOperator(
+ node,
+ ts.TypeFlags.BooleanLike,
+ 'boolean',
+ 'Using !! on a boolean',
+ true,
+ );
+ },
+ 'UnaryExpression[operator = "+"]'(node: TSESTree.UnaryExpression): void {
+ handleUnaryOperator(
+ node,
+ ts.TypeFlags.NumberLike,
+ 'number',
+ 'Using the unary + operator on a number',
+ false,
+ );
+ },
+ 'UnaryExpression[operator = "~"] > UnaryExpression[operator = "~"]'(
+ node: TSESTree.UnaryExpression,
+ ): void {
+ handleUnaryOperator(
+ node,
+ ts.TypeFlags.NumberLike,
+ 'number',
+ 'Using ~~ on a number',
+ true,
+ );
+ },
+ };
+ },
+});
diff --git a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts
index d0b31a5e8446..2dd9ca6b5d2e 100644
--- a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts
+++ b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts
@@ -200,7 +200,7 @@ export default createRule({
receiverProperty.key.type === AST_NODE_TYPES.TemplateLiteral &&
receiverProperty.key.quasis.length === 1
) {
- key = String(receiverProperty.key.quasis[0].value.cooked);
+ key = receiverProperty.key.quasis[0].value.cooked;
} else {
// can't figure out the name, so skip it
continue;
diff --git a/packages/eslint-plugin/src/rules/prefer-return-this-type.ts b/packages/eslint-plugin/src/rules/prefer-return-this-type.ts
index fff92141fe12..7d7b14044cfb 100644
--- a/packages/eslint-plugin/src/rules/prefer-return-this-type.ts
+++ b/packages/eslint-plugin/src/rules/prefer-return-this-type.ts
@@ -62,7 +62,7 @@ export default createRule({
function isThisSpecifiedInParameters(originalFunc: FunctionLike): boolean {
const firstArg = originalFunc.params.at(0);
- return !!(
+ return (
firstArg?.type === AST_NODE_TYPES.Identifier && firstArg.name === 'this'
);
}
diff --git a/packages/eslint-plugin/src/util/getWrappingFixer.ts b/packages/eslint-plugin/src/util/getWrappingFixer.ts
index 2ad20e1d4893..6431b5955040 100644
--- a/packages/eslint-plugin/src/util/getWrappingFixer.ts
+++ b/packages/eslint-plugin/src/util/getWrappingFixer.ts
@@ -23,11 +23,11 @@ interface WrappingFixerParams {
* Receives multiple arguments if there are multiple innerNodes.
* E.g. ``code => `${code} != null` ``
*/
- wrap: (...code: string[]) => string;
+ wrap?: (...code: string[]) => string;
}
/**
- * Wraps node with some code. Adds parenthesis as necessary.
+ * Wraps node with some code. Adds parentheses as necessary.
* @returns Fixer which adds the specified code and parens if necessary.
*/
export function getWrappingFixer(
@@ -55,6 +55,10 @@ export function getWrappingFixer(
return code;
});
+ if (!wrap) {
+ return fixer.replaceText(node, innerCodes.join(''));
+ }
+
// do the wrapping
let code = wrap(...innerCodes);
@@ -105,7 +109,7 @@ export function getMovedNodeCode(params: {
}
/**
- * Check if a node will always have the same precedence if it's parent changes.
+ * Check if a node will always have the same precedence if its parent changes.
*/
export function isStrongPrecedenceNode(innerNode: TSESTree.Node): boolean {
return (
diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-conversion.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-conversion.shot
new file mode 100644
index 000000000000..1231d273a43f
--- /dev/null
+++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-conversion.shot
@@ -0,0 +1,49 @@
+Incorrect
+
+String('123');
+~~~~~~ Passing a string to String() does not change the type or value of the string.
+'123'.toString();
+ ~~~~~~~~~~ Calling a string's .toString() method does not change the type or value of the string.
+'' + '123';
+~~~~~ Concatenating '' with a string does not change the type or value of the string.
+'123' + '';
+ ~~~~~ Concatenating a string with '' does not change the type or value of the string.
+
+Number(123);
+~~~~~~ Passing a number to Number() does not change the type or value of the number.
++123;
+~ Using the unary + operator on a number does not change the type or value of the number.
+~~123;
+~~ Using ~~ on a number does not change the type or value of the number.
+
+Boolean(true);
+~~~~~~~ Passing a boolean to Boolean() does not change the type or value of the boolean.
+!!true;
+~~ Using !! on a boolean does not change the type or value of the boolean.
+
+BigInt(BigInt(1));
+~~~~~~ Passing a bigint to BigInt() does not change the type or value of the bigint.
+
+let str = '123';
+str += '';
+~~~~~~~~~ Concatenating a string with '' does not change the type or value of the string.
+
+Correct
+
+function foo(bar: string | number) {
+ String(bar);
+ bar.toString();
+ '' + bar;
+ bar + '';
+
+ Number(bar);
+ +bar;
+ ~~bar;
+
+ Boolean(bar);
+ !!bar;
+
+ BigInt(1);
+
+ bar += '';
+}
diff --git a/packages/eslint-plugin/tests/rules/await-thenable.test.ts b/packages/eslint-plugin/tests/rules/await-thenable.test.ts
index 1860db956c3e..e64f0a5f1f3c 100644
--- a/packages/eslint-plugin/tests/rules/await-thenable.test.ts
+++ b/packages/eslint-plugin/tests/rules/await-thenable.test.ts
@@ -4,7 +4,6 @@ import rule from '../../src/rules/await-thenable';
import { getFixturesRootDir } from '../RuleTester';
const rootDir = getFixturesRootDir();
-const messageId = 'await';
const ruleTester = new RuleTester({
languageOptions: {
@@ -348,7 +347,7 @@ class C {
errors: [
{
line: 1,
- messageId,
+ messageId: 'await',
suggestions: [
{
messageId: 'removeAwait',
@@ -363,7 +362,7 @@ class C {
errors: [
{
line: 1,
- messageId,
+ messageId: 'await',
suggestions: [
{
messageId: 'removeAwait',
@@ -378,7 +377,7 @@ class C {
errors: [
{
line: 1,
- messageId,
+ messageId: 'await',
suggestions: [
{
messageId: 'removeAwait',
@@ -393,7 +392,7 @@ class C {
errors: [
{
line: 1,
- messageId,
+ messageId: 'await',
suggestions: [
{
messageId: 'removeAwait',
@@ -411,7 +410,7 @@ await new NonPromise();
errors: [
{
line: 3,
- messageId,
+ messageId: 'await',
suggestions: [
{
messageId: 'removeAwait',
@@ -438,7 +437,7 @@ async function test() {
errors: [
{
line: 8,
- messageId,
+ messageId: 'await',
suggestions: [
{
messageId: 'removeAwait',
@@ -465,7 +464,7 @@ await callback?.();
errors: [
{
line: 3,
- messageId,
+ messageId: 'await',
suggestions: [
{
messageId: 'removeAwait',
@@ -486,7 +485,7 @@ await obj.a?.b?.();
errors: [
{
line: 3,
- messageId,
+ messageId: 'await',
suggestions: [
{
messageId: 'removeAwait',
@@ -507,7 +506,7 @@ await obj?.a.b.c?.();
errors: [
{
line: 3,
- messageId,
+ messageId: 'await',
suggestions: [
{
messageId: 'removeAwait',
diff --git a/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts b/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts
index bbba24e7b75c..ce97e7e6a8a2 100644
--- a/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts
+++ b/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts
@@ -5,8 +5,6 @@ import rule from '../../src/rules/no-array-constructor';
const ruleTester = new RuleTester();
-const messageId = 'useLiteral';
-
ruleTester.run('no-array-constructor', rule, {
valid: [
'new Array(x);',
@@ -40,7 +38,7 @@ ruleTester.run('no-array-constructor', rule, {
code: 'new Array();',
errors: [
{
- messageId,
+ messageId: 'useLiteral',
type: AST_NODE_TYPES.NewExpression,
},
],
@@ -50,7 +48,7 @@ ruleTester.run('no-array-constructor', rule, {
code: 'Array();',
errors: [
{
- messageId,
+ messageId: 'useLiteral',
type: AST_NODE_TYPES.CallExpression,
},
],
@@ -60,7 +58,7 @@ ruleTester.run('no-array-constructor', rule, {
code: 'Array?.();',
errors: [
{
- messageId,
+ messageId: 'useLiteral',
type: AST_NODE_TYPES.CallExpression,
},
],
@@ -70,7 +68,7 @@ ruleTester.run('no-array-constructor', rule, {
code: '/* a */ /* b */ Array /* c */ /* d */ /* e */ /* f */?.(); /* g */ /* h */',
errors: [
{
- messageId,
+ messageId: 'useLiteral',
type: AST_NODE_TYPES.CallExpression,
},
],
@@ -80,7 +78,7 @@ ruleTester.run('no-array-constructor', rule, {
code: 'new Array(x, y);',
errors: [
{
- messageId,
+ messageId: 'useLiteral',
type: AST_NODE_TYPES.NewExpression,
},
],
@@ -90,7 +88,7 @@ ruleTester.run('no-array-constructor', rule, {
code: 'Array(x, y);',
errors: [
{
- messageId,
+ messageId: 'useLiteral',
type: AST_NODE_TYPES.CallExpression,
},
],
@@ -100,7 +98,7 @@ ruleTester.run('no-array-constructor', rule, {
code: 'Array?.(x, y);',
errors: [
{
- messageId,
+ messageId: 'useLiteral',
type: AST_NODE_TYPES.CallExpression,
},
],
@@ -110,7 +108,7 @@ ruleTester.run('no-array-constructor', rule, {
code: '/* a */ /* b */ Array /* c */ /* d */ /* e */ /* f */?.(x, y); /* g */ /* h */',
errors: [
{
- messageId,
+ messageId: 'useLiteral',
type: AST_NODE_TYPES.CallExpression,
},
],
@@ -120,7 +118,7 @@ ruleTester.run('no-array-constructor', rule, {
code: 'new Array(0, 1, 2);',
errors: [
{
- messageId,
+ messageId: 'useLiteral',
type: AST_NODE_TYPES.NewExpression,
},
],
@@ -130,7 +128,7 @@ ruleTester.run('no-array-constructor', rule, {
code: 'Array(0, 1, 2);',
errors: [
{
- messageId,
+ messageId: 'useLiteral',
type: AST_NODE_TYPES.CallExpression,
},
],
@@ -140,7 +138,7 @@ ruleTester.run('no-array-constructor', rule, {
code: 'Array?.(0, 1, 2);',
errors: [
{
- messageId,
+ messageId: 'useLiteral',
type: AST_NODE_TYPES.CallExpression,
},
],
@@ -156,7 +154,7 @@ ruleTester.run('no-array-constructor', rule, {
`,
errors: [
{
- messageId,
+ messageId: 'useLiteral',
type: AST_NODE_TYPES.CallExpression,
},
],
@@ -174,7 +172,7 @@ new Array(0, 1, 2);
`,
errors: [
{
- messageId,
+ messageId: 'useLiteral',
type: AST_NODE_TYPES.NewExpression,
},
],
diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts
index 80ba657442a2..91a153d7dd33 100644
--- a/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts
+++ b/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts
@@ -15,8 +15,6 @@ const ruleTester = new RuleTester({
},
});
-const messageId = 'unnecessaryQualifier';
-
ruleTester.run('no-unnecessary-qualifier', rule, {
valid: [
`
@@ -95,7 +93,7 @@ namespace A {
`,
errors: [
{
- messageId,
+ messageId: 'unnecessaryQualifier',
type: AST_NODE_TYPES.Identifier,
},
],
@@ -115,7 +113,7 @@ namespace A {
`,
errors: [
{
- messageId,
+ messageId: 'unnecessaryQualifier',
type: AST_NODE_TYPES.Identifier,
},
],
@@ -137,7 +135,7 @@ namespace A {
`,
errors: [
{
- messageId,
+ messageId: 'unnecessaryQualifier',
type: AST_NODE_TYPES.Identifier,
},
],
@@ -161,7 +159,7 @@ namespace A {
`,
errors: [
{
- messageId,
+ messageId: 'unnecessaryQualifier',
type: AST_NODE_TYPES.TSQualifiedName,
},
],
@@ -185,7 +183,7 @@ namespace A {
`,
errors: [
{
- messageId,
+ messageId: 'unnecessaryQualifier',
type: AST_NODE_TYPES.TSQualifiedName,
},
],
@@ -209,7 +207,7 @@ namespace A {
`,
errors: [
{
- messageId,
+ messageId: 'unnecessaryQualifier',
type: AST_NODE_TYPES.MemberExpression,
},
],
@@ -231,7 +229,7 @@ enum A {
`,
errors: [
{
- messageId,
+ messageId: 'unnecessaryQualifier',
type: AST_NODE_TYPES.Identifier,
},
],
@@ -253,7 +251,7 @@ namespace Foo {
`,
errors: [
{
- messageId,
+ messageId: 'unnecessaryQualifier',
type: AST_NODE_TYPES.MemberExpression,
},
],
@@ -275,7 +273,7 @@ declare module './foo' {
`,
errors: [
{
- messageId,
+ messageId: 'unnecessaryQualifier',
type: AST_NODE_TYPES.Identifier,
},
],
diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-conversion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-conversion.test.ts
new file mode 100644
index 000000000000..e0aaadacee2d
--- /dev/null
+++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-conversion.test.ts
@@ -0,0 +1,724 @@
+import { RuleTester } from '@typescript-eslint/rule-tester';
+
+import rule from '../../src/rules/no-unnecessary-type-conversion';
+import { getFixturesRootDir } from '../RuleTester';
+
+const rootDir = getFixturesRootDir();
+
+const ruleTester = new RuleTester({
+ languageOptions: {
+ parserOptions: {
+ project: './tsconfig.json',
+ tsconfigRootDir: rootDir,
+ },
+ },
+});
+
+ruleTester.run('no-unnecessary-type-conversion', rule, {
+ valid: [
+ // standard type conversions are valid
+ 'String(1);',
+ '(1).toString();',
+ '`${1}`;',
+ "'' + 1;",
+ "1 + '';",
+ `
+ let str = 1;
+ str += '';
+ `,
+ "Number('2');",
+ "+'2';",
+ "~~'2';",
+ 'Boolean(0);',
+ '!!0;',
+ 'BigInt(3);',
+
+ // things that are not type conversion idioms (but look similar) are valid
+ "new String('asdf');",
+ 'new Number(2);',
+ 'new Boolean(true);',
+ '!false;',
+ '~2;',
+ `
+ function String(value: unknown) {
+ return value;
+ }
+ String('asdf');
+ export {};
+ `,
+ `
+ function Number(value: unknown) {
+ return value;
+ }
+ Number(2);
+ export {};
+ `,
+ `
+ function Boolean(value: unknown) {
+ return value;
+ }
+ Boolean(true);
+ export {};
+ `,
+ `
+ function BigInt(value: unknown) {
+ return value;
+ }
+ BigInt(3n);
+ export {};
+ `,
+ `
+ function toString(value: unknown) {
+ return value;
+ }
+ toString('asdf');
+ `,
+ `
+ export {};
+ declare const toString: string;
+ toString.toUpperCase();
+ `,
+
+ // using type conversion idioms to unbox boxed primitives is valid
+ 'String(new String());',
+ 'new String().toString();',
+ "'' + new String();",
+ "new String() + '';",
+ `
+ let str = new String();
+ str += '';
+ `,
+ 'Number(new Number());',
+ '+new Number();',
+ '~~new Number();',
+ 'Boolean(new Boolean());',
+ '!!new Boolean();',
+ ],
+
+ invalid: [
+ {
+ code: "String('asdf');",
+ errors: [
+ {
+ column: 1,
+ endColumn: 7,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: "'asdf';",
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: "'asdf' satisfies string;",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: "'asdf'.toString();",
+ errors: [
+ {
+ column: 8,
+ endColumn: 18,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: "'asdf';",
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: "'asdf' satisfies string;",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: "'' + 'asdf';",
+ errors: [
+ {
+ column: 1,
+ endColumn: 6,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: "'asdf';",
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: "'asdf' satisfies string;",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: "'asdf' + '';",
+ errors: [
+ {
+ column: 7,
+ endColumn: 12,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: "'asdf';",
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: "'asdf' satisfies string;",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: `
+let str = 'asdf';
+str += '';
+ `,
+ errors: [
+ {
+ column: 1,
+ endColumn: 10,
+ endLine: 3,
+ line: 3,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: `
+let str = 'asdf';
+
+ `,
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: `
+let str = 'asdf';
+str satisfies string;
+ `,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: `
+let str = 'asdf';
+'asdf' + (str += '');
+ `,
+ errors: [
+ {
+ column: 11,
+ endColumn: 20,
+ endLine: 3,
+ line: 3,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: `
+let str = 'asdf';
+'asdf' + (str);
+ `,
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: `
+let str = 'asdf';
+'asdf' + (str satisfies string);
+ `,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: 'Number(123);',
+ errors: [
+ {
+ column: 1,
+ endColumn: 7,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: '123;',
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: '123 satisfies number;',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: '+123;',
+ errors: [
+ {
+ column: 1,
+ endColumn: 2,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: '123;',
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: '123 satisfies number;',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: '~~123;',
+ errors: [
+ {
+ column: 1,
+ endColumn: 3,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: '123;',
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: '123 satisfies number;',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: 'Boolean(true);',
+ errors: [
+ {
+ column: 1,
+ endColumn: 8,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: 'true;',
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: 'true satisfies boolean;',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: '!!true;',
+ errors: [
+ {
+ column: 1,
+ endColumn: 3,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: 'true;',
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: 'true satisfies boolean;',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: 'BigInt(3n);',
+ errors: [
+ {
+ column: 1,
+ endColumn: 7,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: '3n;',
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: '3n satisfies bigint;',
+ },
+ ],
+ },
+ ],
+ },
+
+ // using type conversion idioms on generics that extend primitives is invalid
+ {
+ code: `
+ function f(x: T) {
+ return String(x);
+ }
+ `,
+ errors: [
+ {
+ column: 18,
+ endColumn: 24,
+ endLine: 3,
+ line: 3,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: `
+ function f(x: T) {
+ return x;
+ }
+ `,
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: `
+ function f(x: T) {
+ return x satisfies string;
+ }
+ `,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: `
+ function f(x: T) {
+ return Number(x);
+ }
+ `,
+ errors: [
+ {
+ column: 18,
+ endColumn: 24,
+ endLine: 3,
+ line: 3,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: `
+ function f(x: T) {
+ return x;
+ }
+ `,
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: `
+ function f(x: T) {
+ return x satisfies number;
+ }
+ `,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: `
+ function f(x: T) {
+ return Boolean(x);
+ }
+ `,
+ errors: [
+ {
+ column: 18,
+ endColumn: 25,
+ endLine: 3,
+ line: 3,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: `
+ function f(x: T) {
+ return x;
+ }
+ `,
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: `
+ function f(x: T) {
+ return x satisfies boolean;
+ }
+ `,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: `
+ function f(x: T) {
+ return BigInt(x);
+ }
+ `,
+ errors: [
+ {
+ column: 18,
+ endColumn: 24,
+ endLine: 3,
+ line: 3,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: `
+ function f(x: T) {
+ return x;
+ }
+ `,
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: `
+ function f(x: T) {
+ return x satisfies bigint;
+ }
+ `,
+ },
+ ],
+ },
+ ],
+ },
+
+ // make sure fixes preserve parentheses in cases where logic would otherwise break
+ {
+ code: "String('a' + 'b').length;",
+ errors: [
+ {
+ column: 1,
+ endColumn: 7,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: "('a' + 'b').length;",
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: "(('a' + 'b') satisfies string).length;",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: "('a' + 'b').toString().length;",
+ errors: [
+ {
+ column: 13,
+ endColumn: 23,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: "('a' + 'b').length;",
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: "(('a' + 'b') satisfies string).length;",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: '2 * +(2 + 2);',
+ errors: [
+ {
+ column: 5,
+ endColumn: 6,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: '2 * (2 + 2);',
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: '2 * ((2 + 2) satisfies number);',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: '2 * Number(2 + 2);',
+ errors: [
+ {
+ column: 5,
+ endColumn: 11,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: '2 * (2 + 2);',
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: '2 * ((2 + 2) satisfies number);',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: '2 * ~~(2 + 2);',
+ errors: [
+ {
+ column: 5,
+ endColumn: 7,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: '2 * (2 + 2);',
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: '2 * ((2 + 2) satisfies number);',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: 'false && !!(false || true);',
+ errors: [
+ {
+ column: 10,
+ endColumn: 12,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: 'false && (false || true);',
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: 'false && ((false || true) satisfies boolean);',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: 'false && Boolean(false || true);',
+ errors: [
+ {
+ column: 10,
+ endColumn: 17,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: 'false && (false || true);',
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: 'false && ((false || true) satisfies boolean);',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: '2n * BigInt(2n + 2n);',
+ errors: [
+ {
+ column: 6,
+ endColumn: 12,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: '2n * (2n + 2n);',
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: '2n * ((2n + 2n) satisfies bigint);',
+ },
+ ],
+ },
+ ],
+ },
+
+ // make sure suggestions add parentheses in cases where syntax would otherwise break
+ {
+ code: `
+ let str = 'asdf';
+ String(str).length;
+ `,
+ errors: [
+ {
+ column: 9,
+ endColumn: 15,
+ endLine: 3,
+ line: 3,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: `
+ let str = 'asdf';
+ str.length;
+ `,
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: `
+ let str = 'asdf';
+ (str satisfies string).length;
+ `,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: `
+ let str = 'asdf';
+ str.toString().length;
+ `,
+ errors: [
+ {
+ column: 13,
+ endColumn: 23,
+ messageId: 'unnecessaryTypeConversion',
+ suggestions: [
+ {
+ messageId: 'suggestRemove',
+ output: `
+ let str = 'asdf';
+ str.length;
+ `,
+ },
+ {
+ messageId: 'suggestSatisfies',
+ output: `
+ let str = 'asdf';
+ (str satisfies string).length;
+ `,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+});
diff --git a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts
index da1999e93e82..2b6ddbdb9870 100644
--- a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts
+++ b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts
@@ -4,7 +4,6 @@ import rule from '../../src/rules/promise-function-async';
import { getFixturesRootDir } from '../RuleTester';
const rootDir = getFixturesRootDir();
-const messageId = 'missingAsync';
const ruleTester = new RuleTester({
languageOptions: {
@@ -238,7 +237,7 @@ function returnsAny(): any {
`,
errors: [
{
- messageId,
+ messageId: 'missingAsync',
},
],
options: [
@@ -256,7 +255,7 @@ function returnsUnknown(): unknown {
`,
errors: [
{
- messageId,
+ messageId: 'missingAsync',
},
],
options: [
@@ -274,7 +273,7 @@ const nonAsyncPromiseFunctionExpressionA = function (p: Promise) {
`,
errors: [
{
- messageId,
+ messageId: 'missingAsync',
},
],
output: `
@@ -291,7 +290,7 @@ const nonAsyncPromiseFunctionExpressionB = function () {
`,
errors: [
{
- messageId,
+ messageId: 'missingAsync',
},
],
output: `
@@ -308,7 +307,7 @@ function nonAsyncPromiseFunctionDeclarationA(p: Promise) {
`,
errors: [
{
- messageId,
+ messageId: 'missingAsync',
},
],
output: `
@@ -325,7 +324,7 @@ function nonAsyncPromiseFunctionDeclarationB() {
`,
errors: [
{
- messageId,
+ messageId: 'missingAsync',
},
],
output: `
@@ -340,7 +339,7 @@ const nonAsyncPromiseArrowFunctionA = (p: Promise) => p;
`,
errors: [
{
- messageId,
+ messageId: 'missingAsync',
},
],
output: `
@@ -353,7 +352,7 @@ const nonAsyncPromiseArrowFunctionB = () => new Promise();
`,
errors: [
{
- messageId,
+ messageId: 'missingAsync',
},
],
output: `
@@ -371,7 +370,7 @@ const functions = {
errors: [
{
line: 3,
- messageId,
+ messageId: 'missingAsync',
},
],
output: `
@@ -397,11 +396,11 @@ class Test {
errors: [
{
line: 3,
- messageId,
+ messageId: 'missingAsync',
},
{
line: 7,
- messageId,
+ messageId: 'missingAsync',
},
],
output: `
@@ -437,15 +436,15 @@ class Test {
errors: [
{
line: 2,
- messageId,
+ messageId: 'missingAsync',
},
{
line: 6,
- messageId,
+ messageId: 'missingAsync',
},
{
line: 13,
- messageId,
+ messageId: 'missingAsync',
},
],
options: [
@@ -492,15 +491,15 @@ class Test {
errors: [
{
line: 2,
- messageId,
+ messageId: 'missingAsync',
},
{
line: 10,
- messageId,
+ messageId: 'missingAsync',
},
{
line: 13,
- messageId,
+ messageId: 'missingAsync',
},
],
options: [
@@ -547,15 +546,15 @@ class Test {
errors: [
{
line: 6,
- messageId,
+ messageId: 'missingAsync',
},
{
line: 10,
- messageId,
+ messageId: 'missingAsync',
},
{
line: 13,
- messageId,
+ messageId: 'missingAsync',
},
],
options: [
@@ -602,15 +601,15 @@ class Test {
errors: [
{
line: 2,
- messageId,
+ messageId: 'missingAsync',
},
{
line: 6,
- messageId,
+ messageId: 'missingAsync',
},
{
line: 10,
- messageId,
+ messageId: 'missingAsync',
},
],
options: [
@@ -645,7 +644,7 @@ const returnAllowedType = () => new PromiseType();
errors: [
{
line: 4,
- messageId,
+ messageId: 'missingAsync',
},
],
options: [
@@ -671,7 +670,7 @@ function foo(): Promise | SPromise {
errors: [
{
line: 3,
- messageId,
+ messageId: 'missingAsync',
},
],
options: [
@@ -697,7 +696,7 @@ class Test {
}
}
`,
- errors: [{ column: 3, line: 4, messageId }],
+ errors: [{ column: 3, line: 4, messageId: 'missingAsync' }],
output: `
class Test {
@decorator
@@ -723,9 +722,9 @@ class Test {
}
`,
errors: [
- { column: 3, line: 4, messageId },
- { column: 3, line: 7, messageId },
- { column: 3, line: 10, messageId },
+ { column: 3, line: 4, messageId: 'missingAsync' },
+ { column: 3, line: 7, messageId: 'missingAsync' },
+ { column: 3, line: 10, messageId: 'missingAsync' },
],
output: `
class Test {
@@ -764,17 +763,17 @@ class Foo {
{
column: 3,
line: 3,
- messageId,
+ messageId: 'missingAsync',
},
{
column: 3,
line: 7,
- messageId,
+ messageId: 'missingAsync',
},
{
column: 3,
line: 12,
- messageId,
+ messageId: 'missingAsync',
},
],
output: `
@@ -806,7 +805,7 @@ const foo = {
{
column: 3,
line: 3,
- messageId,
+ messageId: 'missingAsync',
},
],
output: `
@@ -905,7 +904,7 @@ function overloadingThatCanReturnPromise(
`,
errors: [
{
- messageId,
+ messageId: 'missingAsync',
},
],
output: `
@@ -928,7 +927,7 @@ function overloadingThatIncludeAny(a?: boolean): any | number {
`,
errors: [
{
- messageId,
+ messageId: 'missingAsync',
},
],
options: [{ allowAny: false }],
@@ -943,7 +942,7 @@ function overloadingThatIncludeUnknown(a?: boolean): unknown | number {
`,
errors: [
{
- messageId,
+ messageId: 'missingAsync',
},
],
options: [{ allowAny: false }],
diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-conversion.shot b/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-conversion.shot
new file mode 100644
index 000000000000..42f81875ed94
--- /dev/null
+++ b/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-conversion.shot
@@ -0,0 +1,10 @@
+
+# SCHEMA:
+
+[]
+
+
+# TYPES:
+
+/** No options declared */
+type Options = [];
\ No newline at end of file
diff --git a/packages/website/src/components/ast/HiddenItem.tsx b/packages/website/src/components/ast/HiddenItem.tsx
index 39aa997bfe54..dd3591fc28ed 100644
--- a/packages/website/src/components/ast/HiddenItem.tsx
+++ b/packages/website/src/components/ast/HiddenItem.tsx
@@ -42,7 +42,7 @@ export default function HiddenItem({
value.map(([key], index) => (
{index > 0 && ', '}
- {String(key)}
+ {key}
))
)}
diff --git a/packages/website/src/components/config/ConfigEditor.tsx b/packages/website/src/components/config/ConfigEditor.tsx
index 66411357347a..2ffc94af3294 100644
--- a/packages/website/src/components/config/ConfigEditor.tsx
+++ b/packages/website/src/components/config/ConfigEditor.tsx
@@ -35,7 +35,7 @@ function filterConfig(
return options
.map(group => ({
fields: group.fields.filter(item =>
- String(item.key.toLowerCase()).includes(filter.toLowerCase()),
+ item.key.toLowerCase().includes(filter.toLowerCase()),
),
heading: group.heading,
}))