Skip to content

feat(eslint-plugin): add no-dynamic-delete rule #1058

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
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
3 changes: 2 additions & 1 deletion packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
| [`@typescript-eslint/await-thenable`](./docs/rules/await-thenable.md) | Disallows awaiting a value that is not a Thenable | :heavy_check_mark: | | :thought_balloon: |
| [`@typescript-eslint/ban-ts-ignore`](./docs/rules/ban-ts-ignore.md) | Bans “// @ts-ignore” comments from being used | :heavy_check_mark: | | |
| [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Bans specific types from being used | :heavy_check_mark: | :wrench: | |
| [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :wrench: | |
| [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :wrench: | |
| [`@typescript-eslint/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | |
| [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names | :heavy_check_mark: | | |
| [`@typescript-eslint/consistent-type-assertions`](./docs/rules/consistent-type-assertions.md) | Enforces consistent usage of type assertions. | :heavy_check_mark: | | |
Expand All @@ -160,6 +160,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
| [`@typescript-eslint/member-naming`](./docs/rules/member-naming.md) | Enforces naming conventions for class members by visibility | | | |
| [`@typescript-eslint/member-ordering`](./docs/rules/member-ordering.md) | Require a consistent member declaration order | | | |
| [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | |
| [`@typescript-eslint/no-dynamic-delete`](./docs/rules/no-dynamic-delete.md) | Bans usage of the delete operator with computed key expressions. | | | |
| [`@typescript-eslint/no-empty-function`](./docs/rules/no-empty-function.md) | Disallow empty functions | :heavy_check_mark: | | |
| [`@typescript-eslint/no-empty-interface`](./docs/rules/no-empty-interface.md) | Disallow the declaration of empty interfaces | :heavy_check_mark: | | |
| [`@typescript-eslint/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type | :heavy_check_mark: | :wrench: | |
Expand Down
27 changes: 27 additions & 0 deletions packages/eslint-plugin/docs/rules/no-dynamic-delete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# no-dynamic-delete

Bans usage of the delete operator with computed key expressions.

## Rule Details

Deleting dynamically computed keys is dangerous and not well optimized.

Also consider using a `Map` or `Set` if you’re storing collections of objects. Using
`Object`s can cause occasional edge case bugs, such as if a key is named “hasOwnProperty”.

### Options

Not configurable

## When Not To Use It

If you require deleting computed object property keys, consider
setting the property value to `undefined` instead, or creating a new
object without the property you wish to delete.

If those alternatives do not work for you, you should use `delete` and
disable this rule.

## Related to

- TSLint: [`no-dynamic-delete`](https://palantir.github.io/tslint/rules/no-dynamic-delete/)
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/configs/all.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@typescript-eslint/member-ordering": "error",
"no-array-constructor": "off",
"@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-dynamic-delete": "error",
"no-empty-function": "off",
"@typescript-eslint/no-empty-function": "error",
"@typescript-eslint/no-empty-interface": "error",
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 @@ -62,6 +62,7 @@ import typedef from './typedef';
import unboundMethod from './unbound-method';
import unifiedSignatures from './unified-signatures';
import useDefaultTypeParameter from './no-unnecessary-type-arguments';
import noDynamicDelete from './no-dynamic-delete';

export default {
'adjacent-overload-signatures': adjacentOverloadSignatures,
Expand All @@ -84,6 +85,7 @@ export default {
'member-naming': memberNaming,
'member-ordering': memberOrdering,
'no-array-constructor': noArrayConstructor,
'no-dynamic-delete': noDynamicDelete,
'no-empty-function': noEmptyFunction,
'no-empty-interface': noEmptyInterface,
'no-explicit-any': noExplicitAny,
Expand Down
37 changes: 37 additions & 0 deletions packages/eslint-plugin/src/rules/no-dynamic-delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { TSESTree } from '@typescript-eslint/experimental-utils';
import * as util from '../util';

export default util.createRule({
name: 'no-dynamic-delete',
meta: {
type: 'problem',
docs: {
description:
'Bans usage of the delete operator with computed key expressions.',
category: 'Possible Errors',
recommended: false,
requiresTypeChecking: false,
},
messages: {
noDynamicDelete: 'Do not delete dynamically computed property keys.',
},
schema: [],
},
defaultOptions: [null],
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
defaultOptions: [null],
defaultOptions: [],

create(context) {
return {
UnaryExpression(node: TSESTree.UnaryExpression): void {
if (node.operator !== 'delete') {
return;
}

if ((node.argument as TSESTree.MemberExpression).computed) {
Copy link
Member

Choose a reason for hiding this comment

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

prefer a physical check to a type cast, as it increases safety

Suggested change
if ((node.argument as TSESTree.MemberExpression).computed) {
if (node.argument.type === TSESTree.MemberExpression && node.argument.computed) {

context.report({
node,
messageId: 'noDynamicDelete',
});
}
},
};
},
});
77 changes: 77 additions & 0 deletions packages/eslint-plugin/tests/rules/no-dynamic-delete.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import rule from '../../src/rules/no-dynamic-delete';
import { RuleTester } from '../RuleTester';

const ruleTester = new RuleTester({
parser: '@typescript-eslint/parser',
});

const messageId = 'noDynamicDelete';

ruleTester.run('no-dynamic-delete', rule, {
valid: [
{
code: 'delete container.a;',
},
{
code: 'delete container.b;',
},
],
invalid: [
{
code: 'delete container["a" + "b"];',
errors: [
{
messageId,
},
],
},
{
code: 'delete container["c"]',
errors: [
{
messageId,
},
],
},
{
code: 'delete container[7]',
errors: [
{
messageId,
},
],
},
{
code: 'delete container[+7]',
errors: [
{
messageId,
},
],
},
{
code: 'delete container[-7]',
errors: [
{
messageId,
},
],
},
{
code: 'delete container[NaN]',
errors: [
{
messageId,
},
],
},
{
code: 'delete container[getComputedName()]',
errors: [
{
messageId,
},
],
},
],
});