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, }))