Skip to content

feat(eslint-plugin): [no-array-delete] add rule #5852

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6ec086d
Complete Super Method Checking
mahdi-farnia Oct 18, 2022
31be88c
Update packages/eslint-plugin/docs/rules/call-super-on-override.md
mahdi-farnia Oct 19, 2022
5138d1d
Update packages/eslint-plugin/src/rules/call-super-on-override.ts
mahdi-farnia Oct 19, 2022
b8444c3
Update packages/eslint-plugin/src/rules/call-super-on-override.ts
mahdi-farnia Oct 19, 2022
a31ade2
Unnecessary ignoreMethods Option Removed
mahdi-farnia Oct 19, 2022
2b65003
Support For Literal Method Names
mahdi-farnia Oct 19, 2022
ab81978
Merge branch 'typescript-eslint:main' into main
mahdi-farnia Oct 19, 2022
1fe9429
Merge branch 'typescript-eslint:main' into main
mahdi-farnia Oct 19, 2022
7f588b3
Merge remote-tracking branch 'refs/remotes/origin/main'
mahdi-farnia Oct 19, 2022
35c527c
Merge branch 'typescript-eslint:main' into main
mahdi-farnia Oct 19, 2022
e95c87f
Resolve Failed CI Tests & TopLevel Option Removed
mahdi-farnia Oct 19, 2022
4e9da68
Rule no-in-array
mahdi-farnia Oct 20, 2022
75c1979
Rule no-array-delete Completed
mahdi-farnia Oct 20, 2022
8d31382
Merge branch 'main' into no-array-delete
JoshuaKGoldberg Nov 13, 2022
f397a33
remove unneeded files from other branch
mahdi-farnia Jan 26, 2023
c32bb0a
remove unneeded files from other branch
mahdi-farnia Jan 26, 2023
0b59dc6
Apply Suggested Changes
mahdi-farnia Feb 26, 2023
bf6b1b6
Merge branch 'main' into no-array-delete
mahdi-farnia Feb 26, 2023
b08dd24
Merge branch 'main' into no-array-delete
mahdi-farnia Mar 14, 2023
b7d976c
apply suggested changes
mahdi-farnia Mar 14, 2023
264df53
Merge branch 'no-array-delete' of github.com:mahdi-farnia/typescript-…
mahdi-farnia Mar 14, 2023
ab27b88
more test & fix sequence expr suggestions
mahdi-farnia Mar 14, 2023
41e88c2
Merge branch 'main' into no-array-delete
mahdi-farnia Mar 16, 2023
e4689f0
Fix Unit Test & Lint Test Errors
mahdi-farnia Mar 16, 2023
7f3a68d
Fix spacing causing unit test error
mahdi-farnia Mar 17, 2023
7fe1ee9
Merge branch 'main' into no-array-delete
mahdi-farnia Apr 7, 2023
de0a0b8
fix linting problem in test file
mahdi-farnia Apr 7, 2023
a3290ec
reduce amount of type request
mahdi-farnia Apr 7, 2023
b5d99e0
apply suggested changes
mahdi-farnia Apr 7, 2023
8230f09
Update no-array-delete.md
bradzacher May 6, 2023
2392654
make doc more clearer
mahdi-farnia Jul 21, 2023
04ec031
fix(no-array-delete): buggy suggestion
mahdi-farnia Jul 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions packages/eslint-plugin/docs/rules/no-array-delete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
description: 'Disallow delete operator for arrays.'
---

> 🛑 This file is source code, not the primary documentation location! 🛑
>
> See **https://typescript-eslint.io/rules/no-array-delete** for documentation.

In JavaScript, using the `delete` operator on an array makes the given index empty and leaves the array length unchanged.
See [MDN's _Deleting array elements_ documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#deleting_array_elements) for more information.
This can sometimes cause problems with performance and unexpected behaviors around array loops and index accesses.

## Examples

<!--tabs-->

### ❌ Incorrect

```ts
declare const array: unknown[];
declare const index: number;

delete array[index];
```

### ✅ Correct

```ts
declare const array: unknown[];
declare const index: number;

array.splice(index, 1);
```

## When Not To Use It

If you don't care about having empty element in array, then you will not need this rule.
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/configs/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export = {
'@typescript-eslint/naming-convention': 'error',
'no-array-constructor': 'off',
'@typescript-eslint/no-array-constructor': 'error',
'@typescript-eslint/no-array-delete': 'error',
'@typescript-eslint/no-base-to-string': 'error',
'@typescript-eslint/no-confusing-non-null-assertion': 'error',
'@typescript-eslint/no-confusing-void-expression': 'error',
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/configs/strict.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export = {
'@typescript-eslint/consistent-type-definitions': 'warn',
'dot-notation': 'off',
'@typescript-eslint/dot-notation': 'warn',
'@typescript-eslint/no-array-delete': 'warn',
'@typescript-eslint/no-base-to-string': 'warn',
'@typescript-eslint/no-confusing-non-null-assertion': 'warn',
'@typescript-eslint/no-duplicate-enum-values': 'warn',
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import memberOrdering from './member-ordering';
import methodSignatureStyle from './method-signature-style';
import namingConvention from './naming-convention';
import noArrayConstructor from './no-array-constructor';
import noArrayDelete from './no-array-delete';
import noBaseToString from './no-base-to-string';
import confusingNonNullAssertionLikeNotEqual from './no-confusing-non-null-assertion';
import noConfusingVoidExpression from './no-confusing-void-expression';
Expand Down Expand Up @@ -170,6 +171,7 @@ export default {
'method-signature-style': methodSignatureStyle,
'naming-convention': namingConvention,
'no-array-constructor': noArrayConstructor,
'no-array-delete': noArrayDelete,
'no-base-to-string': noBaseToString,
'no-confusing-non-null-assertion': confusingNonNullAssertionLikeNotEqual,
'no-confusing-void-expression': noConfusingVoidExpression,
Expand Down
91 changes: 91 additions & 0 deletions packages/eslint-plugin/src/rules/no-array-delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import { unionTypeParts } from 'tsutils';
import * as ts from 'typescript';

import * as util from '../util';
Copy link
Contributor

@StyleShit StyleShit Oct 23, 2023

Choose a reason for hiding this comment

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

util should probably be switched to named imports


type MessageIds = 'arrayDelete' | 'suggestFunctionalDelete';

export default util.createRule<[], MessageIds>({
name: 'no-array-delete',
meta: {
hasSuggestions: true,
docs: {
description: 'Disallow delete operator for arrays',
recommended: 'strict',
requiresTypeChecking: true,
},
messages: {
arrayDelete: 'Using the delete operator on an array is dangerous.',
suggestFunctionalDelete:
'Using Array.slice instead of delete keyword prevents empty array element.',
},
schema: [],
type: 'problem',
fixable: 'code',
},
defaultOptions: [],
create(context) {
const parserServices = util.getParserServices(context);
const checker = parserServices.program.getTypeChecker();

return {
"UnaryExpression[operator='delete'] > MemberExpression[computed]"(
node: TSESTree.MemberExpressionComputedName & {
parent: TSESTree.UnaryExpression;
},
): void {
const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node);

const target = originalNode.getChildAt(0);
const key = originalNode.getChildAt(2);

const targetType = util.getConstrainedTypeAtLocation(checker, target);

if (!isTypeArrayTypeOrArrayInUnionOfTypes(targetType, checker)) {
return;
}

const keyType = util.getConstrainedTypeAtLocation(checker, key);

if (!util.isTypeFlagSet(keyType, ts.TypeFlags.NumberLike)) {
return;
}

context.report({
node,
messageId: 'arrayDelete',
suggest: [
{
messageId: 'suggestFunctionalDelete',
fix(fixer): TSESLint.RuleFix | null {
const requiresParens =
node.property.type === AST_NODE_TYPES.SequenceExpression;
const keyText = key.getText();

if (util.isTypeFlagSet(keyType, ts.TypeFlags.String)) {
return null;
}

return fixer.replaceText(
node.parent,
`${target.getText()}.splice(${
requiresParens ? `(${keyText})` : keyText
}, 1)`,
);
},
},
],
});
},
};
},
});

function isTypeArrayTypeOrArrayInUnionOfTypes(
type: ts.Type,
checker: ts.TypeChecker,
): boolean {
return unionTypeParts(type).some(checker.isArrayType);
}
Loading