-
-
Notifications
You must be signed in to change notification settings - Fork 477
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(linter): implement @typescript-eslint/no-dynamic-delete (#3971)
Related issue: #2180 original implementation - code: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/rules/no-dynamic-delete.ts - test: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/tests/rules/no-dynamic-delete.test.ts - doc: https://typescript-eslint.io/rules/no-dynamic-delete/
- Loading branch information
Showing
3 changed files
with
258 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
164 changes: 164 additions & 0 deletions
164
crates/oxc_linter/src/rules/typescript/no_dynamic_delete.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
use oxc_ast::{ast::Expression, AstKind}; | ||
use oxc_diagnostics::OxcDiagnostic; | ||
use oxc_macros::declare_oxc_lint; | ||
use oxc_span::Span; | ||
use oxc_syntax::operator::UnaryOperator; | ||
|
||
use crate::{context::LintContext, rule::Rule, AstNode}; | ||
|
||
#[derive(Debug, Default, Clone)] | ||
pub struct NoDynamicDelete; | ||
|
||
declare_oxc_lint!( | ||
/// ### What it does | ||
/// Disallow using the delete operator on computed key expressions. | ||
/// | ||
/// ### Why is this bad? | ||
/// Deleting dynamically computed keys can be dangerous and in some cases not well optimized. | ||
/// Using the delete operator on keys that aren't runtime constants could be a sign that you're using the wrong data structures. | ||
/// Consider using a Map or Set if you’re using an object as a key-value collection. | ||
/// | ||
/// ### Example | ||
/// ```javascript | ||
/// const container: { [i: string]: 0 } = {}; | ||
/// delete container['aa' + 'b']; | ||
/// ``` | ||
NoDynamicDelete, | ||
restriction, | ||
); | ||
|
||
fn no_dynamic_delete_diagnostic(span0: Span) -> OxcDiagnostic { | ||
OxcDiagnostic::warn( | ||
"typescript-eslint(no-dynamic-delete): Do not delete dynamically computed property keys.", | ||
) | ||
.with_help("Disallow using the `delete` operator on computed key expressions") | ||
.with_label(span0) | ||
} | ||
|
||
impl Rule for NoDynamicDelete { | ||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { | ||
let AstKind::UnaryExpression(expr) = node.kind() else { return }; | ||
if !matches!(expr.operator, UnaryOperator::Delete) { | ||
return; | ||
} | ||
|
||
let Expression::ComputedMemberExpression(computed_expr) = &expr.argument else { return }; | ||
let inner_expression = computed_expr.expression.get_inner_expression(); | ||
if inner_expression.is_string_literal() || inner_expression.is_number_literal() { | ||
return; | ||
} | ||
|
||
if let Expression::UnaryExpression(unary_expr) = &inner_expression { | ||
if unary_expr.operator == UnaryOperator::UnaryNegation | ||
&& unary_expr.argument.is_number_literal() | ||
{ | ||
return; | ||
} | ||
} | ||
ctx.diagnostic(no_dynamic_delete_diagnostic(expr.span)); | ||
} | ||
} | ||
|
||
#[test] | ||
fn test() { | ||
use crate::tester::Tester; | ||
|
||
let pass = vec![ | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container.aaa; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container.delete; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container[7]; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container[-7]; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container['-Infinity']; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container['+Infinity']; | ||
", | ||
" | ||
const value = 1; | ||
delete value; | ||
", | ||
" | ||
const value = 1; | ||
delete -value; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container['aaa']; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container['delete']; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container['NaN']; | ||
", | ||
" | ||
const container = {}; | ||
delete container[('aaa')] | ||
", | ||
]; | ||
|
||
let fail = vec![ | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container['aa' + 'b']; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container[+7]; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container[-Infinity]; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container[+Infinity]; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container[NaN]; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
const name = 'name'; | ||
delete container[name]; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
const getName = () => 'aaa'; | ||
delete container[getName()]; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
const name = { foo: { bar: 'bar' } }; | ||
delete container[name.foo.bar]; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container[+'Infinity']; | ||
", | ||
" | ||
const container: { [i: string]: 0 } = {}; | ||
delete container[typeof 1]; | ||
", | ||
]; | ||
|
||
Tester::new(NoDynamicDelete::NAME, pass, fail).test_and_snapshot(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
--- | ||
source: crates/oxc_linter/src/tester.rs | ||
--- | ||
⚠ typescript-eslint(no-dynamic-delete): Do not delete dynamically computed property keys. | ||
╭─[no_dynamic_delete.tsx:3:10] | ||
2 │ const container: { [i: string]: 0 } = {}; | ||
3 │ delete container['aa' + 'b']; | ||
· ──────────────────────────── | ||
4 │ | ||
╰──── | ||
help: Disallow using the `delete` operator on computed key expressions | ||
|
||
⚠ typescript-eslint(no-dynamic-delete): Do not delete dynamically computed property keys. | ||
╭─[no_dynamic_delete.tsx:3:10] | ||
2 │ const container: { [i: string]: 0 } = {}; | ||
3 │ delete container[+7]; | ||
· ──────────────────── | ||
4 │ | ||
╰──── | ||
help: Disallow using the `delete` operator on computed key expressions | ||
|
||
⚠ typescript-eslint(no-dynamic-delete): Do not delete dynamically computed property keys. | ||
╭─[no_dynamic_delete.tsx:3:10] | ||
2 │ const container: { [i: string]: 0 } = {}; | ||
3 │ delete container[-Infinity]; | ||
· ─────────────────────────── | ||
4 │ | ||
╰──── | ||
help: Disallow using the `delete` operator on computed key expressions | ||
|
||
⚠ typescript-eslint(no-dynamic-delete): Do not delete dynamically computed property keys. | ||
╭─[no_dynamic_delete.tsx:3:10] | ||
2 │ const container: { [i: string]: 0 } = {}; | ||
3 │ delete container[+Infinity]; | ||
· ─────────────────────────── | ||
4 │ | ||
╰──── | ||
help: Disallow using the `delete` operator on computed key expressions | ||
|
||
⚠ typescript-eslint(no-dynamic-delete): Do not delete dynamically computed property keys. | ||
╭─[no_dynamic_delete.tsx:3:10] | ||
2 │ const container: { [i: string]: 0 } = {}; | ||
3 │ delete container[NaN]; | ||
· ───────────────────── | ||
4 │ | ||
╰──── | ||
help: Disallow using the `delete` operator on computed key expressions | ||
|
||
⚠ typescript-eslint(no-dynamic-delete): Do not delete dynamically computed property keys. | ||
╭─[no_dynamic_delete.tsx:4:10] | ||
3 │ const name = 'name'; | ||
4 │ delete container[name]; | ||
· ────────────────────── | ||
5 │ | ||
╰──── | ||
help: Disallow using the `delete` operator on computed key expressions | ||
|
||
⚠ typescript-eslint(no-dynamic-delete): Do not delete dynamically computed property keys. | ||
╭─[no_dynamic_delete.tsx:4:10] | ||
3 │ const getName = () => 'aaa'; | ||
4 │ delete container[getName()]; | ||
· ─────────────────────────── | ||
5 │ | ||
╰──── | ||
help: Disallow using the `delete` operator on computed key expressions | ||
|
||
⚠ typescript-eslint(no-dynamic-delete): Do not delete dynamically computed property keys. | ||
╭─[no_dynamic_delete.tsx:4:10] | ||
3 │ const name = { foo: { bar: 'bar' } }; | ||
4 │ delete container[name.foo.bar]; | ||
· ────────────────────────────── | ||
5 │ | ||
╰──── | ||
help: Disallow using the `delete` operator on computed key expressions | ||
|
||
⚠ typescript-eslint(no-dynamic-delete): Do not delete dynamically computed property keys. | ||
╭─[no_dynamic_delete.tsx:3:10] | ||
2 │ const container: { [i: string]: 0 } = {}; | ||
3 │ delete container[+'Infinity']; | ||
· ───────────────────────────── | ||
4 │ | ||
╰──── | ||
help: Disallow using the `delete` operator on computed key expressions | ||
|
||
⚠ typescript-eslint(no-dynamic-delete): Do not delete dynamically computed property keys. | ||
╭─[no_dynamic_delete.tsx:3:10] | ||
2 │ const container: { [i: string]: 0 } = {}; | ||
3 │ delete container[typeof 1]; | ||
· ────────────────────────── | ||
4 │ | ||
╰──── | ||
help: Disallow using the `delete` operator on computed key expressions |