Skip to content
Merged
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
107 changes: 55 additions & 52 deletions crates/oxc_linter/src/rules/eslint/no_redeclare.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
use oxc_ast::{
AstKind,
ast::{BindingIdentifier, BindingPatternKind},
};
use javascript_globals::GLOBALS;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use oxc_syntax::symbol::SymbolId;

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

fn no_redeclare_diagnostic(id_name: &str, decl_span: Span, re_decl_span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn(format!("'{id_name}' is already defined.")).with_labels([
decl_span.label(format!("'{id_name}' is already defined.")),
fn no_redeclare_diagnostic(name: &str, decl_span: Span, re_decl_span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn(format!("'{name}' is already defined.")).with_labels([
decl_span.label(format!("'{name}' is already defined.")),
re_decl_span.label("It can not be redeclare here."),
])
}

fn no_redeclare_as_builtin_in_diagnostic(builtin_name: &str, span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn(format!(
"'{builtin_name}' is already defined as a built-in global variable."
))
.with_label(
span.label(format!("'{builtin_name}' is already defined as a built-in global variable.")),
)
fn no_redeclare_as_builtin_in_diagnostic(name: &str, span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn(format!("'{name}' is already defined as a built-in global variable."))
.with_label(span)
}

#[derive(Debug, Default, Clone)]
Expand All @@ -33,17 +26,35 @@ pub struct NoRedeclare {
declare_oxc_lint!(
/// ### What it does
///
/// Disallow variable redeclaration
/// This rule disallows redeclaring variables within the same scope, ensuring that each variable
/// is declared only once. It helps avoid confusion and unintended behavior in code.
///
/// ### Why is this bad?
///
/// n JavaScript, it’s possible to redeclare the same variable name using var. This can lead to confusion as to where the variable is actually declared and initialized.
/// Redeclaring variables in the same scope can lead to unexpected behavior, overwriting existing values,
/// and making the code harder to understand and maintain.
///
/// ### Example
/// ### Examples
///
/// Examples of **incorrect** code for this rule:
/// ```javascript
/// var a = 3;
/// var a = 10;
/// ```
///
/// Examples of **correct** code for this rule:
/// ```javascript
/// var a = 3;
/// a = 10;
/// ```
///
/// ### Options
///
/// #### builtinGlobals
///
/// `{ type: bool, default: false }`
///
/// When set `true`, it flags redeclaring built-in globals (e.g., `let Object = 1;`).
NoRedeclare,
eslint,
pedantic
Expand All @@ -61,40 +72,22 @@ impl Rule for NoRedeclare {
}

fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext) {
let symbol_table = ctx.scoping();
let decl_node_id = symbol_table.symbol_declaration(symbol_id);
match ctx.nodes().kind(decl_node_id) {
AstKind::VariableDeclarator(var) => {
if let BindingPatternKind::BindingIdentifier(ident) = &var.id.kind {
let symbol_name = symbol_table.symbol_name(symbol_id);
if symbol_name == ident.name.as_str() {
for span in ctx.scoping().symbol_redeclarations(symbol_id) {
self.report_diagnostic(ctx, *span, ident);
}
}
}
}
AstKind::FormalParameter(param) => {
if let BindingPatternKind::BindingIdentifier(ident) = &param.pattern.kind {
let symbol_name = symbol_table.symbol_name(symbol_id);
if symbol_name == ident.name.as_str() {
for span in ctx.scoping().symbol_redeclarations(symbol_id) {
self.report_diagnostic(ctx, *span, ident);
}
}
}
}
_ => {}
let name = ctx.scoping().symbol_name(symbol_id);
let is_builtin = self.built_in_globals
&& (GLOBALS["builtin"].contains_key(name) || ctx.globals().is_enabled(name));

let decl_span = ctx.scoping().symbol_span(symbol_id);

if is_builtin {
ctx.diagnostic(no_redeclare_as_builtin_in_diagnostic(name, decl_span));
}
}
}

impl NoRedeclare {
fn report_diagnostic(&self, ctx: &LintContext, span: Span, ident: &BindingIdentifier) {
if self.built_in_globals && ctx.env_contains_var(&ident.name) {
ctx.diagnostic(no_redeclare_as_builtin_in_diagnostic(ident.name.as_str(), ident.span));
} else {
ctx.diagnostic(no_redeclare_diagnostic(ident.name.as_str(), ident.span, span));
for span in ctx.scoping().symbol_redeclarations(symbol_id) {
if is_builtin {
ctx.diagnostic(no_redeclare_as_builtin_in_diagnostic(name, *span));
} else {
ctx.diagnostic(no_redeclare_diagnostic(name, decl_span, *span));
}
}
}
}
Expand Down Expand Up @@ -144,21 +137,31 @@ fn test() {
("class C { static { var a; { var a; } } }", None),
("class C { static { { var a; } var a; } }", None),
("class C { static { { var a; } { var a; } } }", None),
// ("var Object = 0;", Some(serde_json::json!([{ "builtinGlobals": true }]))),
(
"var Object = 0; var Object = 0; var globalThis = 0;",
Some(serde_json::json!([{ "builtinGlobals": true }])),
),
(
"var a; var {a = 0, b: Object = 0} = {};",
Some(serde_json::json!([{ "builtinGlobals": true }])),
),
// ("var globalThis = 0;", Some(serde_json::json!([{ "builtinGlobals": true }]))),
(
"var a; var {a = 0, b: globalThis = 0} = {};",
Some(serde_json::json!([{ "builtinGlobals": true }])),
),
("function f() { var a; var a; }", None),
("function f(a) { var a; }", None),
("function f(a, b = 1) { var a; var b;}", None),
("function f() { var a; if (test) { var a; } }", None),
("for (var a, a;;);", None),
];

Tester::new(NoRedeclare::NAME, NoRedeclare::PLUGIN, pass, fail).test_and_snapshot();

let fail = vec![(
"var foo;",
Some(serde_json::json!([{ "builtinGlobals": true }])),
Some(serde_json::json!({ "globals": { "foo": false }})),
)];

Tester::new(NoRedeclare::NAME, NoRedeclare::PLUGIN, vec![], fail).test();
}
44 changes: 41 additions & 3 deletions crates/oxc_linter/src/snapshots/eslint_no_redeclare.snap
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,24 @@ source: crates/oxc_linter/src/tester.rs
· ╰── 'a' is already defined.
╰────

⚠ eslint(no-redeclare): 'Object' is already defined as a built-in global variable.
╭─[no_redeclare.tsx:1:5]
1 │ var Object = 0; var Object = 0; var globalThis = 0;
· ──────
╰────

⚠ eslint(no-redeclare): 'Object' is already defined as a built-in global variable.
╭─[no_redeclare.tsx:1:21]
1 │ var Object = 0; var Object = 0; var globalThis = 0;
· ──────
╰────

⚠ eslint(no-redeclare): 'globalThis' is already defined as a built-in global variable.
╭─[no_redeclare.tsx:1:37]
1 │ var Object = 0; var Object = 0; var globalThis = 0;
· ──────────
╰────

⚠ eslint(no-redeclare): 'a' is already defined.
╭─[no_redeclare.tsx:1:5]
1 │ var a; var {a = 0, b: Object = 0} = {};
Expand All @@ -131,6 +149,12 @@ source: crates/oxc_linter/src/tester.rs
· ╰── 'a' is already defined.
╰────

⚠ eslint(no-redeclare): 'Object' is already defined as a built-in global variable.
╭─[no_redeclare.tsx:1:23]
1 │ var a; var {a = 0, b: Object = 0} = {};
· ──────
╰────

⚠ eslint(no-redeclare): 'a' is already defined.
╭─[no_redeclare.tsx:1:5]
1 │ var a; var {a = 0, b: globalThis = 0} = {};
Expand All @@ -139,6 +163,12 @@ source: crates/oxc_linter/src/tester.rs
· ╰── 'a' is already defined.
╰────

⚠ eslint(no-redeclare): 'globalThis' is already defined as a built-in global variable.
╭─[no_redeclare.tsx:1:23]
1 │ var a; var {a = 0, b: globalThis = 0} = {};
· ──────────
╰────

⚠ eslint(no-redeclare): 'a' is already defined.
╭─[no_redeclare.tsx:1:20]
1 │ function f() { var a; var a; }
Expand All @@ -149,12 +179,20 @@ source: crates/oxc_linter/src/tester.rs

⚠ eslint(no-redeclare): 'a' is already defined.
╭─[no_redeclare.tsx:1:12]
1 │ function f(a) { var a; }
· ┬ ┬
· │ ╰── It can not be redeclare here.
1 │ function f(a, b = 1) { var a; var b;}
· ┬
· │ ╰── It can not be redeclare here.
· ╰── 'a' is already defined.
╰────

⚠ eslint(no-redeclare): 'b' is already defined.
╭─[no_redeclare.tsx:1:15]
1 │ function f(a, b = 1) { var a; var b;}
· ┬ ┬
· │ ╰── It can not be redeclare here.
· ╰── 'b' is already defined.
╰────

⚠ eslint(no-redeclare): 'a' is already defined.
╭─[no_redeclare.tsx:1:20]
1 │ function f() { var a; if (test) { var a; } }
Expand Down