Skip to content
31 changes: 15 additions & 16 deletions packages/eslint-plugin/src/rules/no-useless-default-assignment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ export default createRule<[], MessageId>({
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)) {
Expand All @@ -60,10 +55,7 @@ export default createRule<[], MessageId>({
): ts.Type | null {
const symbol = objectType.getProperty(propertyName);
if (!symbol) {
if (isNoUncheckedIndexedAccess) {
return null;
}
return objectType.getStringIndexType() ?? null;
return null;
}
return checker.getTypeOfSymbol(symbol);
}
Expand All @@ -79,10 +71,6 @@ export default createRule<[], MessageId>({
}
}

if (isNoUncheckedIndexedAccess) {
return null;
}

return arrayType.getNumberIndexType() ?? null;
}

Expand Down Expand Up @@ -118,6 +106,13 @@ export default createRule<[], MessageId>({
}

const signatures = contextualType.getCallSignatures();
if (
signatures.length === 0 ||
signatures[0].getDeclaration() === tsFunc
) {
return;
}

const params = signatures[0].getParameters();
if (paramIndex < params.length) {
const paramSymbol = params[paramIndex];
Expand Down Expand Up @@ -152,12 +147,16 @@ export default createRule<[], MessageId>({
return;
}

const elementIndex = parent.elements.indexOf(node);
const elementType = getArrayElementType(sourceType, elementIndex);
if (!elementType) {
if (!checker.isTupleType(sourceType)) {
return;
}

const tupleArgs = checker.getTypeArguments(sourceType);
const elementIndex = parent.elements.indexOf(node);
if (elementIndex < 0 || elementIndex >= tupleArgs.length) {
return;
}
const elementType = tupleArgs[elementIndex];
if (!canBeUndefined(elementType)) {
reportUselessDefault(node, 'property', 'uselessDefaultAssignment');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,46 @@ ruleTester.run('no-useless-default-assignment', rule, {
},
},
},
`
declare const g: Array<string>;
const [foo = ''] = g;
`,
`
declare const g: Record<string, string>;
const { foo = '' } = g;
`,
`
declare const h: { [key: string]: string };
const { bar = '' } = h;
`,
// https://github.com/typescript-eslint/typescript-eslint/issues/11849
`
type Merge = boolean | ((incoming: string[]) => void);

const policy: { merge: Merge } = {
merge: (incoming: string[] = []) => {
incoming;
},
};
`,
// https://github.com/typescript-eslint/typescript-eslint/issues/11846
`
const [a, b = ''] = 'somestr'.split('.');
`,
// https://github.com/typescript-eslint/typescript-eslint/issues/11846
`
declare const params: string[];
const [c = '123'] = params;
`,
// https://github.com/typescript-eslint/typescript-eslint/issues/11846
`
declare function useCallback<T>(callback: T);
useCallback((value: number[] = []) => {});
`,
`
declare const tuple: [string];
const [a, b = 'default'] = tuple;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(praise) TIL that TS allows out-of-bounds tuple destructuring if a default value is present.

Comically it means that

const [a, outOfBounds = undefined] = ["foo"] as const;

is kind of a false positive if you really squint, but I think let's completely ignore that edge case as niche and unimportant.

`,
],
invalid: [
{
Expand Down Expand Up @@ -459,62 +499,5 @@ ruleTester.run('no-useless-default-assignment', rule, {
function foo({ a }) {}
`,
},
{
code: `
declare const g: Record<string, string>;
const { hello = '' } = g;
`,
errors: [
{
column: 25,
data: { type: 'property' },
endColumn: 27,
line: 3,
messageId: 'uselessDefaultAssignment',
},
],
output: `
declare const g: Record<string, string>;
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<string>;
const [foo = ''] = g;
`,
errors: [
{
column: 22,
data: { type: 'property' },
endColumn: 24,
line: 3,
messageId: 'uselessDefaultAssignment',
},
],
output: `
declare const g: Array<string>;
const [foo] = g;
`,
},
],
});