oxc-project/oxc · warning · OxcDiagnostic

'{name}' is already declared in the upper scope.

Error message

'{name}' is already declared in the upper scope.

What it means

Diagnostic from oxlint's `no-shadow` rule. It fires when an inner scope declares a binding with the same name as one visible in an outer scope, making the outer binding unreachable inside. Labels point at both declarations; for TypeScript enum members an extra note explains that member initializers resolve inside the enum scope.

Source

Thrown at crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs:37

    node::NodeId,
    symbol::{SymbolFlags, SymbolId},
};

use crate::{
    context::LintContext,
    rule::{DefaultRuleConfig, Rule},
};

pub use options::{HoistOption, NoShadowConfig};

pub fn no_shadow_diagnostic(
    span: Span,
    name: &str,
    shadowed_span: Span,
    is_enum_member: bool,
) -> OxcDiagnostic {
    let diagnostic =
        OxcDiagnostic::warn(format!("'{name}' is already declared in the upper scope."))
            .with_help(format!(
                "Consider renaming '{name}' to avoid shadowing the variable from the outer scope."
            ))
            .with_labels([
                span.label(format!("'{name}' is declared here")),
                shadowed_span.label("shadowed declaration is here"),
            ]);

    if is_enum_member {
        diagnostic.with_note(format!(
            "Enum members are added to the enum scope, so references to '{name}' in enum member initializers resolve to this member instead of the declaration in the upper scope."
        ))
    } else {
        diagnostic
    }
}

pub fn no_shadow_global_diagnostic(span: Span, name: &str) -> OxcDiagnostic {

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Rename the inner binding to something specific (`userId` instead of `id`).
  2. Rename the outer variable if the inner name is the clearer one.
  3. Adjust `hoist` if function declarations declared after use are being flagged unexpectedly.
  4. Suppress with an `oxlint-ignore no-shadow` comment for intentional shadowing (e.g. minified-style code).

Example fix

// before
let items = [];
function sync() {
  for (let items of groups) { use(items); }
}

// after
let items = [];
function sync() {
  for (let group of groups) { use(group); }
}
Defensive patterns

Strategy: validation

Validate before calling

// Identify names declared in nested function scopes that match outer declarations
function shadowedNames(src) {
  const outer = new Set([...src.matchAll(/(?:let|const|var)\s+([A-Za-z_$][\w$]*)/g)].map(m => m[1]));
  const inner = [...src.matchAll(/function\s*\w*\s*\([^)]*\)|=>|\bcatch\s*\(/g)];
  // refine with a real parser for production use; heuristic for quick checks
  return inner.length && [...outer];
}

Prevention

When it happens

Trigger: `let x = 1; function f() { let x = 2; }`; callback params reusing outer names (`items.map(item => ...)` with an outer `item`); TS `enum E { A = B }` where `B` is an outer binding that members shadow. The `hoist` option (`functions`/`all`/`never`) and `builtinGlobals` tune what counts as shadowed.

Common situations: Nested callbacks over similarly-named data; catch blocks declaring `error` when an outer `error` exists; strict codebases enabling no-shadow in pedantic configs after an upgrade.

Related errors


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