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..de0d7f1002bb
--- /dev/null
+++ b/packages/eslint-plugin/docs/rules/no-unnecessary-type-conversion.mdx
@@ -0,0 +1,79 @@
+---
+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 provides several commonly used idioms to convert values to a specific type:
+
+- Primitive coercion (e.g. `Boolean(value)`, `String(value)`): using a built-in primitive function
+- String concatenation (e.g. `value + ''`): turning a value into a string
+- Unary coercion (e.g. `+value`, `!!value`): using a built-in operator
+- The `.toString()` method defined on many types
+
+These conversions are unnecessary if the value is already of that type.
+
+## 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..dfa4a57de456
--- /dev/null
+++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-conversion.ts
@@ -0,0 +1,362 @@
+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;
+ const builtInTypeFlags = {
+ BigInt: ts.TypeFlags.BigIntLike,
+ Boolean: ts.TypeFlags.BooleanLike,
+ Number: ts.TypeFlags.NumberLike,
+ String: ts.TypeFlags.StringLike,
+ };
+
+ if (
+ nodeCallee.type !== AST_NODE_TYPES.Identifier ||
+ !(nodeCallee.name in builtInTypeFlags)
+ ) {
+ return;
+ }
+
+ const typeFlag =
+ builtInTypeFlags[nodeCallee.name as keyof typeof builtInTypeFlags];
+ const scope = context.sourceCode.getScope(node);
+ const variable = scope.set.get(nodeCallee.name);
+ if (
+ !!variable?.defs.length ||
+ !doesUnderlyingTypeMatchFlag(
+ getConstrainedTypeAtLocation(services, node.arguments[0]),
+ typeFlag,
+ )
+ ) {
+ return;
+ }
+
+ 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,
}))