diff --git a/packages/eslint-plugin/docs/rules/no-useless-default-assignment.mdx b/packages/eslint-plugin/docs/rules/no-useless-default-assignment.mdx new file mode 100644 index 000000000000..258e9b5b812b --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-useless-default-assignment.mdx @@ -0,0 +1,53 @@ +--- +description: 'Disallow default values that will never be used.' +--- + +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-useless-default-assignment** for documentation. + +[Default parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters) and [default values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#default_value) are only used if the parameter or property is `undefined`. +That can happen when a value is missing, or when one is provided and set to `undefined`. +If a non-`undefined` value is guaranteed to be provided, then there is no need to define a default. + +## Examples + + + + +```ts +function Bar({ foo = '' }: { foo: string }) { + return foo; +} + +const { foo = '' } = { foo: 'bar' }; + +const [foo = ''] = ['bar']; + +[1, 2, 3].map((a = 42) => a + 1); +``` + + + + +```ts +function Bar({ foo = '' }: { foo?: string }) { + return foo; +} + +const { foo = '' } = { foo: undefined }; + +const [foo = ''] = [undefined]; + +[1, 2, 3, undefined].map((a = 42) => a + 1); +``` + + + + +## When Not To Use It + +If you use default values defensively against runtime values that bypass type checking, or for documentation purposes, you may want to disable this rule. diff --git a/packages/eslint-plugin/src/configs/eslintrc/all.ts b/packages/eslint-plugin/src/configs/eslintrc/all.ts index 78c8420e2643..11221a74807b 100644 --- a/packages/eslint-plugin/src/configs/eslintrc/all.ts +++ b/packages/eslint-plugin/src/configs/eslintrc/all.ts @@ -120,6 +120,7 @@ export = { '@typescript-eslint/no-use-before-define': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', + '@typescript-eslint/no-useless-default-assignment': 'error', '@typescript-eslint/no-useless-empty-export': 'error', '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/non-nullable-type-assertion-style': '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 6853a151722d..277f99228c1d 100644 --- a/packages/eslint-plugin/src/configs/eslintrc/disable-type-checked.ts +++ b/packages/eslint-plugin/src/configs/eslintrc/disable-type-checked.ts @@ -44,6 +44,7 @@ export = { '@typescript-eslint/no-unsafe-return': 'off', '@typescript-eslint/no-unsafe-type-assertion': 'off', '@typescript-eslint/no-unsafe-unary-minus': 'off', + '@typescript-eslint/no-useless-default-assignment': 'off', '@typescript-eslint/non-nullable-type-assertion-style': 'off', '@typescript-eslint/only-throw-error': 'off', '@typescript-eslint/prefer-destructuring': 'off', diff --git a/packages/eslint-plugin/src/configs/eslintrc/strict-type-checked-only.ts b/packages/eslint-plugin/src/configs/eslintrc/strict-type-checked-only.ts index a2b5b457d01e..c455ac88371b 100644 --- a/packages/eslint-plugin/src/configs/eslintrc/strict-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/eslintrc/strict-type-checked-only.ts @@ -39,6 +39,7 @@ export = { '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', '@typescript-eslint/no-unsafe-unary-minus': 'error', + '@typescript-eslint/no-useless-default-assignment': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', 'prefer-promise-reject-errors': 'off', diff --git a/packages/eslint-plugin/src/configs/eslintrc/strict-type-checked.ts b/packages/eslint-plugin/src/configs/eslintrc/strict-type-checked.ts index ec1523d8b48e..aba8c3006a1a 100644 --- a/packages/eslint-plugin/src/configs/eslintrc/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/eslintrc/strict-type-checked.ts @@ -68,6 +68,7 @@ export = { '@typescript-eslint/no-unused-vars': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', + '@typescript-eslint/no-useless-default-assignment': 'error', '@typescript-eslint/no-wrapper-object-types': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', diff --git a/packages/eslint-plugin/src/configs/flat/all.ts b/packages/eslint-plugin/src/configs/flat/all.ts index 43792419ab3b..7e61ed948ab9 100644 --- a/packages/eslint-plugin/src/configs/flat/all.ts +++ b/packages/eslint-plugin/src/configs/flat/all.ts @@ -134,6 +134,7 @@ export default ( '@typescript-eslint/no-use-before-define': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', + '@typescript-eslint/no-useless-default-assignment': 'error', '@typescript-eslint/no-useless-empty-export': 'error', '@typescript-eslint/no-wrapper-object-types': 'error', '@typescript-eslint/non-nullable-type-assertion-style': '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 5a48e1722775..7603dde91c7a 100644 --- a/packages/eslint-plugin/src/configs/flat/disable-type-checked.ts +++ b/packages/eslint-plugin/src/configs/flat/disable-type-checked.ts @@ -51,6 +51,7 @@ export default ( '@typescript-eslint/no-unsafe-return': 'off', '@typescript-eslint/no-unsafe-type-assertion': 'off', '@typescript-eslint/no-unsafe-unary-minus': 'off', + '@typescript-eslint/no-useless-default-assignment': 'off', '@typescript-eslint/non-nullable-type-assertion-style': 'off', '@typescript-eslint/only-throw-error': 'off', '@typescript-eslint/prefer-destructuring': 'off', diff --git a/packages/eslint-plugin/src/configs/flat/strict-type-checked-only.ts b/packages/eslint-plugin/src/configs/flat/strict-type-checked-only.ts index 043f1aad0cd5..2cddf49a4633 100644 --- a/packages/eslint-plugin/src/configs/flat/strict-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/flat/strict-type-checked-only.ts @@ -52,6 +52,7 @@ export default ( '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', '@typescript-eslint/no-unsafe-unary-minus': 'error', + '@typescript-eslint/no-useless-default-assignment': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', 'prefer-promise-reject-errors': 'off', diff --git a/packages/eslint-plugin/src/configs/flat/strict-type-checked.ts b/packages/eslint-plugin/src/configs/flat/strict-type-checked.ts index b3c0a2391178..4d34eb4925d8 100644 --- a/packages/eslint-plugin/src/configs/flat/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/flat/strict-type-checked.ts @@ -81,6 +81,7 @@ export default ( '@typescript-eslint/no-unused-vars': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', + '@typescript-eslint/no-useless-default-assignment': 'error', '@typescript-eslint/no-wrapper-object-types': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 9ee5903aa5f7..c32c32967519 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -92,6 +92,7 @@ import noUnusedPrivateClassMembers from './no-unused-private-class-members'; import noUnusedVars from './no-unused-vars'; import noUseBeforeDefine from './no-use-before-define'; import noUselessConstructor from './no-useless-constructor'; +import noUselessDefaultAssignment from './no-useless-default-assignment'; import noUselessEmptyExport from './no-useless-empty-export'; import noVarRequires from './no-var-requires'; import noWrapperObjectTypes from './no-wrapper-object-types'; @@ -227,6 +228,7 @@ const rules = { 'no-unused-vars': noUnusedVars, 'no-use-before-define': noUseBeforeDefine, 'no-useless-constructor': noUselessConstructor, + 'no-useless-default-assignment': noUselessDefaultAssignment, 'no-useless-empty-export': noUselessEmptyExport, 'no-var-requires': noVarRequires, 'no-wrapper-object-types': noWrapperObjectTypes, diff --git a/packages/eslint-plugin/src/rules/no-useless-default-assignment.ts b/packages/eslint-plugin/src/rules/no-useless-default-assignment.ts new file mode 100644 index 000000000000..f33631df44a4 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-useless-default-assignment.ts @@ -0,0 +1,263 @@ +import type { TSESTree } from '@typescript-eslint/utils'; + +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import * as tsutils from 'ts-api-utils'; +import * as ts from 'typescript'; + +import { + createRule, + getParserServices, + isFunction, + isTypeAnyType, + isTypeFlagSet, + isTypeUnknownType, + nullThrows, + NullThrowsReasons, +} from '../util'; + +type MessageId = 'uselessDefaultAssignment' | 'uselessUndefined'; + +export default createRule<[], MessageId>({ + name: 'no-useless-default-assignment', + meta: { + type: 'suggestion', + docs: { + description: 'Disallow default values that will never be used', + recommended: 'strict', + requiresTypeChecking: true, + }, + fixable: 'code', + messages: { + uselessDefaultAssignment: + 'Default value is useless because the {{ type }} is not optional.', + uselessUndefined: + 'Default value is useless because it is undefined. Optional {{ type }}s are already undefined by default.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + const services = getParserServices(context); + const checker = services.program.getTypeChecker(); + const compilerOptions = services.program.getCompilerOptions(); + const isNoUncheckedIndexedAccess = tsutils.isCompilerOptionEnabled( + compilerOptions, + 'noUncheckedIndexedAccess', + ); + + function canBeUndefined(type: ts.Type): boolean { + if (isTypeAnyType(type) || isTypeUnknownType(type)) { + return true; + } + return tsutils + .unionConstituents(type) + .some(part => isTypeFlagSet(part, ts.TypeFlags.Undefined)); + } + + function getPropertyType( + objectType: ts.Type, + propertyName: string, + ): ts.Type | null { + const symbol = objectType.getProperty(propertyName); + if (!symbol) { + if (isNoUncheckedIndexedAccess) { + return null; + } + return objectType.getStringIndexType() ?? null; + } + return checker.getTypeOfSymbol(symbol); + } + + function getArrayElementType( + arrayType: ts.Type, + elementIndex: number, + ): ts.Type | null { + if (checker.isTupleType(arrayType)) { + const tupleArgs = checker.getTypeArguments(arrayType); + if (elementIndex < tupleArgs.length) { + return tupleArgs[elementIndex]; + } + } + + if (isNoUncheckedIndexedAccess) { + return null; + } + + return arrayType.getNumberIndexType() ?? null; + } + + function checkAssignmentPattern(node: TSESTree.AssignmentPattern): void { + if ( + node.right.type === AST_NODE_TYPES.Identifier && + node.right.name === 'undefined' + ) { + const type = + node.parent.type === AST_NODE_TYPES.Property || + node.parent.type === AST_NODE_TYPES.ArrayPattern + ? 'property' + : 'parameter'; + reportUselessDefault(node, type, 'uselessUndefined'); + return; + } + + const parent = node.parent; + + if ( + parent.type === AST_NODE_TYPES.ArrowFunctionExpression || + parent.type === AST_NODE_TYPES.FunctionExpression + ) { + const paramIndex = parent.params.indexOf(node); + if (paramIndex !== -1) { + const tsFunc = services.esTreeNodeToTSNodeMap.get(parent); + if (ts.isFunctionLike(tsFunc)) { + const contextualType = checker.getContextualType( + tsFunc as ts.Expression, + ); + if (!contextualType) { + return; + } + + const signatures = contextualType.getCallSignatures(); + const params = signatures[0].getParameters(); + if (paramIndex < params.length) { + const paramSymbol = params[paramIndex]; + if ((paramSymbol.flags & ts.SymbolFlags.Optional) === 0) { + const paramType = checker.getTypeOfSymbol(paramSymbol); + if (!canBeUndefined(paramType)) { + reportUselessDefault( + node, + 'parameter', + 'uselessDefaultAssignment', + ); + } + } + } + } + } + return; + } + + if (parent.type === AST_NODE_TYPES.Property) { + const propertyType = getTypeOfProperty(parent); + if (!propertyType) { + return; + } + + if (!canBeUndefined(propertyType)) { + reportUselessDefault(node, 'property', 'uselessDefaultAssignment'); + } + } else if (parent.type === AST_NODE_TYPES.ArrayPattern) { + const sourceType = getSourceTypeForPattern(parent); + if (!sourceType) { + return; + } + + const elementIndex = parent.elements.indexOf(node); + const elementType = getArrayElementType(sourceType, elementIndex); + if (!elementType) { + return; + } + + if (!canBeUndefined(elementType)) { + reportUselessDefault(node, 'property', 'uselessDefaultAssignment'); + } + } + } + + function getTypeOfProperty(node: TSESTree.Property): ts.Type | null { + const objectPattern = node.parent as TSESTree.ObjectPattern; + const sourceType = getSourceTypeForPattern(objectPattern); + if (!sourceType) { + return null; + } + + const propertyName = getPropertyName(node.key); + if (!propertyName) { + return null; + } + + return getPropertyType(sourceType, propertyName); + } + + function getSourceTypeForPattern(pattern: TSESTree.Node): ts.Type | null { + const parent = nullThrows( + pattern.parent, + NullThrowsReasons.MissingParent, + ); + + if (parent.type === AST_NODE_TYPES.VariableDeclarator && parent.init) { + const tsNode = services.esTreeNodeToTSNodeMap.get(parent.init); + return checker.getTypeAtLocation(tsNode); + } + + if (isFunction(parent)) { + const paramIndex = parent.params.indexOf(pattern as TSESTree.Parameter); + const tsFunc = services.esTreeNodeToTSNodeMap.get(parent); + const signature = nullThrows( + checker.getSignatureFromDeclaration(tsFunc), + NullThrowsReasons.MissingToken('signature', 'function'), + ); + const params = signature.getParameters(); + return checker.getTypeOfSymbol(params[paramIndex]); + } + + if (parent.type === AST_NODE_TYPES.AssignmentPattern) { + return getSourceTypeForPattern(parent); + } + + if (parent.type === AST_NODE_TYPES.Property) { + return getTypeOfProperty(parent); + } + + if (parent.type === AST_NODE_TYPES.ArrayPattern) { + const arrayPattern = parent; + const arrayType = getSourceTypeForPattern(arrayPattern); + if (!arrayType) { + return null; + } + const elementIndex = arrayPattern.elements.indexOf( + pattern as TSESTree.DestructuringPattern, + ); + return getArrayElementType(arrayType, elementIndex); + } + + return null; + } + + function getPropertyName( + key: TSESTree.Expression | TSESTree.PrivateIdentifier, + ): string | null { + switch (key.type) { + case AST_NODE_TYPES.Identifier: + return key.name; + case AST_NODE_TYPES.Literal: + return String(key.value); + default: + return null; + } + } + + function reportUselessDefault( + node: TSESTree.AssignmentPattern, + type: 'parameter' | 'property', + messageId: MessageId, + ): void { + context.report({ + node: node.right, + messageId, + data: { type }, + fix(fixer) { + // Remove from before the = to the end of the default value + // Find the start position (including whitespace before =) + const start = node.left.range[1]; + const end = node.range[1]; + return fixer.removeRange([start, end]); + }, + }); + } + + return { + AssignmentPattern: checkAssignmentPattern, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-useless-default-assignment.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-useless-default-assignment.shot new file mode 100644 index 000000000000..b63ea9a740f0 --- /dev/null +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-useless-default-assignment.shot @@ -0,0 +1,27 @@ +Incorrect + +function Bar({ foo = '' }: { foo: string }) { + ~~ Default value is useless because the property is not optional. + return foo; +} + +const { foo = '' } = { foo: 'bar' }; + ~~ Default value is useless because the property is not optional. + +const [foo = ''] = ['bar']; + ~~ Default value is useless because the property is not optional. + +[1, 2, 3].map((a = 42) => a + 1); + ~~ Default value is useless because the parameter is not optional. + +Correct + +function Bar({ foo = '' }: { foo?: string }) { + return foo; +} + +const { foo = '' } = { foo: undefined }; + +const [foo = ''] = [undefined]; + +[1, 2, 3, undefined].map((a = 42) => a + 1); diff --git a/packages/eslint-plugin/tests/rules/no-useless-default-assignment.test.ts b/packages/eslint-plugin/tests/rules/no-useless-default-assignment.test.ts new file mode 100644 index 000000000000..d5d52b5bd0db --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-useless-default-assignment.test.ts @@ -0,0 +1,520 @@ +import rule from '../../src/rules/no-useless-default-assignment'; +import { createRuleTesterWithTypes, getFixturesRootDir } from '../RuleTester'; + +const rootDir = getFixturesRootDir(); +const ruleTester = createRuleTesterWithTypes(); + +ruleTester.run('no-useless-default-assignment', rule, { + valid: [ + ` + function Bar({ foo = '' }: { foo?: string }) { + return foo; + } + `, + ` + const { foo } = { foo: 'bar' }; + `, + ` + [1, 2, 3, undefined].map((a = 42) => a + 1); + `, + ` + function test(a?: number) { + return a; + } + `, + ` + const obj: { a?: string } = {}; + const { a = 'default' } = obj; + `, + ` + function test(a: string | undefined = 'default') { + return a; + } + `, + ` + (a: string = 'default') => a; + `, + ` + function test(a: string = 'default') { + return a; + } + `, + ` + class C { + public test(a: string = 'default') { + return a; + } + } + `, + ` + const obj: { a: string | undefined } = { a: undefined }; + const { a = 'default' } = obj; + `, + ` + function test(arr: number[] | undefined = []) { + return arr; + } + `, + ` + function Bar({ nested: { foo = '' } = {} }: { nested?: { foo?: string } }) { + return foo; + } + `, + ` + function test(a: any = 'default') { + return a; + } + `, + ` + function test(a: unknown = 'default') { + return a; + } + `, + ` + function test(a = 5) { + return a; + } + `, + ` + function createValidator(): () => void { + return (param = 5) => {}; + } + `, + ` + function Bar({ foo = '' }: { foo: any }) { + return foo; + } + `, + ` + function Bar({ foo = '' }: { foo: unknown }) { + return foo; + } + `, + ` + function getValue(): undefined; + function getValue(box: { value: string }): string; + function getValue({ value = '' }: { value?: string } = {}): string | undefined { + return value; + } + `, + ` + function getValueObject({ value = '' }: Partial<{ value: string }>) { + return value; + } + `, + ` + const { value = 'default' } = someUnknownFunction(); + `, + ` + const [value = 'default'] = someUnknownFunction(); + `, + ` + for (const { value = 'default' } of []) { + } + `, + ` + for (const [value = 'default'] of []) { + } + `, + ` + declare const x: [[number | undefined]]; + const [[a = 1]] = x; + `, + ` + function foo(x: string = '') {} + `, + ` + class C { + method(x: string = '') {} + } + `, + ` + const foo = (x: string = '') => {}; + `, + ` + const obj = { ab: { x: 1 } }; + const { + ['a' + 'b']: { x = 1 }, + } = obj; + `, + ` + const obj = { ab: 1 }; + const { ['a' + 'b']: x = 1 } = obj; + `, + ` + for ([[a = 1]] of []) { + } + `, + { + code: ` + declare const g: Array; + const [foo = ''] = g; + `, + languageOptions: { + parserOptions: { + project: './tsconfig.noUncheckedIndexedAccess.json', + projectService: false, + tsconfigRootDir: rootDir, + }, + }, + }, + { + code: ` + declare const g: Record; + const { foo = '' } = g; + `, + languageOptions: { + parserOptions: { + project: './tsconfig.noUncheckedIndexedAccess.json', + projectService: false, + tsconfigRootDir: rootDir, + }, + }, + }, + { + code: ` + declare const h: { [key: string]: string }; + const { bar = '' } = h; + `, + languageOptions: { + parserOptions: { + project: './tsconfig.noUncheckedIndexedAccess.json', + projectService: false, + tsconfigRootDir: rootDir, + }, + }, + }, + ], + invalid: [ + { + code: ` + function Bar({ foo = '' }: { foo: string }) { + return foo; + } + `, + errors: [ + { + column: 30, + data: { type: 'property' }, + endColumn: 32, + line: 2, + messageId: 'uselessDefaultAssignment', + }, + ], + output: ` + function Bar({ foo }: { foo: string }) { + return foo; + } + `, + }, + { + code: ` + class C { + public method({ foo = '' }: { foo: string }) { + return foo; + } + } + `, + errors: [ + { + column: 33, + data: { type: 'property' }, + endColumn: 35, + line: 3, + messageId: 'uselessDefaultAssignment', + }, + ], + output: ` + class C { + public method({ foo }: { foo: string }) { + return foo; + } + } + `, + }, + { + code: ` + const { 'literal-key': literalKey = 'default' } = { 'literal-key': 'value' }; + `, + errors: [ + { + column: 45, + data: { type: 'property' }, + endColumn: 54, + line: 2, + messageId: 'uselessDefaultAssignment', + }, + ], + output: ` + const { 'literal-key': literalKey } = { 'literal-key': 'value' }; + `, + }, + { + code: ` + [1, 2, 3].map((a = 42) => a + 1); + `, + errors: [ + { + column: 28, + data: { type: 'parameter' }, + endColumn: 30, + line: 2, + messageId: 'uselessDefaultAssignment', + }, + ], + output: ` + [1, 2, 3].map((a) => a + 1); + `, + }, + { + code: ` + function getValue(): undefined; + function getValue(box: { value: string }): string; + function getValue({ value = '' }: { value: string } = {}): string | undefined { + return value; + } + `, + errors: [ + { + column: 37, + data: { type: 'property' }, + endColumn: 39, + line: 4, + messageId: 'uselessDefaultAssignment', + }, + ], + output: ` + function getValue(): undefined; + function getValue(box: { value: string }): string; + function getValue({ value }: { value: string } = {}): string | undefined { + return value; + } + `, + }, + { + code: ` + function getValue([value = '']: [string]) { + return value; + } + `, + errors: [ + { + column: 36, + data: { type: 'property' }, + endColumn: 38, + line: 2, + messageId: 'uselessDefaultAssignment', + }, + ], + output: ` + function getValue([value]: [string]) { + return value; + } + `, + }, + { + code: ` + declare const x: { hello: { world: string } }; + + const { + hello: { world = '' }, + } = x; + `, + errors: [ + { + column: 28, + data: { type: 'property' }, + endColumn: 30, + line: 5, + messageId: 'uselessDefaultAssignment', + }, + ], + output: ` + declare const x: { hello: { world: string } }; + + const { + hello: { world }, + } = x; + `, + }, + { + code: ` + declare const x: { hello: Array<{ world: string }> }; + + const { + hello: [{ world = '' }], + } = x; + `, + errors: [ + { + column: 29, + data: { type: 'property' }, + endColumn: 31, + line: 5, + messageId: 'uselessDefaultAssignment', + }, + ], + output: ` + declare const x: { hello: Array<{ world: string }> }; + + const { + hello: [{ world }], + } = x; + `, + }, + { + code: ` + interface B { + foo: (b: boolean | string) => void; + } + + const h: B = { + foo: (b = false) => {}, + }; + `, + errors: [ + { + column: 21, + data: { type: 'parameter' }, + endColumn: 26, + line: 7, + messageId: 'uselessDefaultAssignment', + }, + ], + output: ` + interface B { + foo: (b: boolean | string) => void; + } + + const h: B = { + foo: (b) => {}, + }; + `, + }, + { + code: ` + function foo(a = undefined) {} + `, + errors: [ + { + column: 26, + data: { type: 'parameter' }, + endColumn: 35, + line: 2, + messageId: 'uselessUndefined', + }, + ], + output: ` + function foo(a) {} + `, + }, + { + code: ` + const { a = undefined } = {}; + `, + errors: [ + { + column: 21, + data: { type: 'property' }, + endColumn: 30, + line: 2, + messageId: 'uselessUndefined', + }, + ], + output: ` + const { a } = {}; + `, + }, + { + code: ` + const [a = undefined] = []; + `, + errors: [ + { + column: 20, + data: { type: 'property' }, + endColumn: 29, + line: 2, + messageId: 'uselessUndefined', + }, + ], + output: ` + const [a] = []; + `, + }, + { + code: ` + function foo({ a = undefined }) {} + `, + errors: [ + { + column: 28, + data: { type: 'property' }, + endColumn: 37, + line: 2, + messageId: 'uselessUndefined', + }, + ], + output: ` + function foo({ a }) {} + `, + }, + { + code: ` + declare const g: Record; + const { hello = '' } = g; + `, + errors: [ + { + column: 25, + data: { type: 'property' }, + endColumn: 27, + line: 3, + messageId: 'uselessDefaultAssignment', + }, + ], + output: ` + declare const g: Record; + const { hello } = g; + `, + }, + { + code: ` + declare const h: { [key: string]: string }; + const { world = '' } = h; + `, + errors: [ + { + column: 25, + data: { type: 'property' }, + endColumn: 27, + line: 3, + messageId: 'uselessDefaultAssignment', + }, + ], + output: ` + declare const h: { [key: string]: string }; + const { world } = h; + `, + }, + { + code: ` + declare const g: Array; + const [foo = ''] = g; + `, + errors: [ + { + column: 22, + data: { type: 'property' }, + endColumn: 24, + line: 3, + messageId: 'uselessDefaultAssignment', + }, + ], + output: ` + declare const g: Array; + const [foo] = g; + `, + }, + ], +}); diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-useless-default-assignment.shot b/packages/eslint-plugin/tests/schema-snapshots/no-useless-default-assignment.shot new file mode 100644 index 000000000000..cdd9f8375858 --- /dev/null +++ b/packages/eslint-plugin/tests/schema-snapshots/no-useless-default-assignment.shot @@ -0,0 +1,10 @@ + +# SCHEMA: + +[] + + +# TYPES: + +/** No options declared */ +type Options = [];