oxc-project/oxc · warning · OxcDiagnostic

Do not delete dynamically computed property keys.

Error message

Do not delete dynamically computed property keys.

What it means

typescript/no-dynamic-delete reports `delete obj[computedKey]` — the delete operator applied to a computed member expression. The rule's note cites V8 behavior: frequent deletions can push objects into dictionary-mode (slow) properties and hurt inline caches. Help text suggests a static key or a Map/Set for dynamic keys.

Source

Thrown at crates/oxc_linter/src/rules/typescript/no_dynamic_delete.rs:44

    /// ```ts
    /// const container: { [i: string]: 0 } = {};
    /// delete container['aa' + 'b'];
    /// ```
    ///
    /// Examples of **correct** code for this rule:
    /// ```ts
    /// const container: { [i: string]: 0 } = {};
    /// delete container.aab;
    /// ```
    NoDynamicDelete,
    typescript,
    restriction,
    version = "0.5.2",
    short_description = "Disallow using the delete operator on computed key expressions.",
);

fn no_dynamic_delete_diagnostic(span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn("Do not delete dynamically computed property keys.")
        .with_help("Use a static property key, or use a Map/Set for dynamic keys.")
        .with_note(
            "Frequent property deletions can move objects to slower dictionary-mode properties and hurt inline-cache optimizations. See: https://v8.dev/blog/fast-properties.",
        )
        .with_label(span)
}

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;

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Use a Map and `cache.delete(key)` — designed for dynamic keys
  2. Use a Set for presence tracking: `set.delete(key)`
  3. If the key set is known, switch to static deletion: `delete obj.fixedKey`
  4. If object shape must be preserved, set to undefined instead of deleting: `obj[key] = undefined` (note: shape stays fast but the key remains)

Example fix

// before
const cache: Record<string, Data> = {};
delete cache[key];

// after
const cache = new Map<string, Data>();
cache.delete(key);
Defensive patterns

Strategy: fallback

Validate before calling

// .oxlintrc.json
{
  "rules": { "typescript/no-dynamic-delete": "warn" }
}

// quick audit for the pattern before adopting the rule:
// rg --type ts "delete\s+\w+\[" src/

Prevention

When it happens

Trigger: Any UnaryExpression with operator `delete` whose argument is a computed member access: `delete cache[key]`, `delete obj[propName]`, `delete map[`${a}-${b}`]`; deleting a static property (`delete obj.key`) is not reported.

Common situations: Hand-rolled caches or registries using plain objects with variable keys; porting Map-like logic from objects; memoization tables that grow and shrink; config maps where entries are removed at runtime.

Related errors


AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20). Data as JSON: /api/errors/0de5e976163cd048. Report an issue: GitHub.