From 14f324ca335f55238b4bbcfd66139e76310a88fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20Gabriel?= Date: Wed, 4 Feb 2026 15:44:24 +0100 Subject: [PATCH 01/15] feat(oxlint): add no-shadow for eslint and typescript --- .../src/generated/rule_runner_impls.rs | 10 + crates/oxc_linter/src/generated/rules_enum.rs | 52 ++- crates/oxc_linter/src/rules.rs | 2 + .../oxc_linter/src/rules/eslint/no_shadow.rs | 211 +++++++++++ .../src/rules/typescript/no_shadow.rs | 349 ++++++++++++++++++ .../src/snapshots/eslint_no_shadow.snap | 103 ++++++ .../src/snapshots/typescript_no_shadow.snap | 130 +++++++ 7 files changed, 855 insertions(+), 2 deletions(-) create mode 100644 crates/oxc_linter/src/rules/eslint/no_shadow.rs create mode 100644 crates/oxc_linter/src/rules/typescript/no_shadow.rs create mode 100644 crates/oxc_linter/src/snapshots/eslint_no_shadow.snap create mode 100644 crates/oxc_linter/src/snapshots/typescript_no_shadow.snap diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index c4ac064ef9a2a..ff97bd30fdc00 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -942,6 +942,11 @@ impl RuleRunner for crate::rules::eslint::no_setter_return::NoSetterReturn { const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } +impl RuleRunner for crate::rules::eslint::no_shadow::NoShadow { + const NODE_TYPES: Option<&AstTypesBitset> = None; + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce; +} + impl RuleRunner for crate::rules::eslint::no_shadow_restricted_names::NoShadowRestrictedNames { const NODE_TYPES: Option<&AstTypesBitset> = None; const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce; @@ -1662,6 +1667,11 @@ impl RuleRunner for crate::rules::typescript::no_restricted_types::NoRestrictedT const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } +impl RuleRunner for crate::rules::typescript::no_shadow::NoShadow { + const NODE_TYPES: Option<&AstTypesBitset> = None; + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce; +} + impl RuleRunner for crate::rules::typescript::no_this_alias::NoThisAlias { const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[ AstType::AssignmentExpression, diff --git a/crates/oxc_linter/src/generated/rules_enum.rs b/crates/oxc_linter/src/generated/rules_enum.rs index bc97be087fa84..5abade2c39d69 100644 --- a/crates/oxc_linter/src/generated/rules_enum.rs +++ b/crates/oxc_linter/src/generated/rules_enum.rs @@ -119,6 +119,7 @@ pub use crate::rules::eslint::no_self_assign::NoSelfAssign as EslintNoSelfAssign pub use crate::rules::eslint::no_self_compare::NoSelfCompare as EslintNoSelfCompare; pub use crate::rules::eslint::no_sequences::NoSequences as EslintNoSequences; pub use crate::rules::eslint::no_setter_return::NoSetterReturn as EslintNoSetterReturn; +pub use crate::rules::eslint::no_shadow::NoShadow as EslintNoShadow; pub use crate::rules::eslint::no_shadow_restricted_names::NoShadowRestrictedNames as EslintNoShadowRestrictedNames; pub use crate::rules::eslint::no_sparse_arrays::NoSparseArrays as EslintNoSparseArrays; pub use crate::rules::eslint::no_template_curly_in_string::NoTemplateCurlyInString as EslintNoTemplateCurlyInString; @@ -479,6 +480,7 @@ pub use crate::rules::typescript::no_non_null_assertion::NoNonNullAssertion as T pub use crate::rules::typescript::no_redundant_type_constituents::NoRedundantTypeConstituents as TypescriptNoRedundantTypeConstituents; pub use crate::rules::typescript::no_require_imports::NoRequireImports as TypescriptNoRequireImports; pub use crate::rules::typescript::no_restricted_types::NoRestrictedTypes as TypescriptNoRestrictedTypes; +pub use crate::rules::typescript::no_shadow::NoShadow as TypescriptNoShadow; pub use crate::rules::typescript::no_this_alias::NoThisAlias as TypescriptNoThisAlias; pub use crate::rules::typescript::no_unnecessary_boolean_literal_compare::NoUnnecessaryBooleanLiteralCompare as TypescriptNoUnnecessaryBooleanLiteralCompare; pub use crate::rules::typescript::no_unnecessary_condition::NoUnnecessaryCondition as TypescriptNoUnnecessaryCondition; @@ -836,6 +838,7 @@ pub enum RuleEnum { EslintNoSelfCompare(EslintNoSelfCompare), EslintNoSequences(EslintNoSequences), EslintNoSetterReturn(EslintNoSetterReturn), + EslintNoShadow(EslintNoShadow), EslintNoShadowRestrictedNames(EslintNoShadowRestrictedNames), EslintNoSparseArrays(EslintNoSparseArrays), EslintNoTemplateCurlyInString(EslintNoTemplateCurlyInString), @@ -938,6 +941,7 @@ pub enum RuleEnum { TypescriptNoRedundantTypeConstituents(TypescriptNoRedundantTypeConstituents), TypescriptNoRequireImports(TypescriptNoRequireImports), TypescriptNoRestrictedTypes(TypescriptNoRestrictedTypes), + TypescriptNoShadow(TypescriptNoShadow), TypescriptNoThisAlias(TypescriptNoThisAlias), TypescriptNoUnnecessaryBooleanLiteralCompare(TypescriptNoUnnecessaryBooleanLiteralCompare), TypescriptNoUnnecessaryCondition(TypescriptNoUnnecessaryCondition), @@ -1517,7 +1521,8 @@ const ESLINT_NO_SELF_ASSIGN_ID: usize = ESLINT_NO_SCRIPT_URL_ID + 1usize; const ESLINT_NO_SELF_COMPARE_ID: usize = ESLINT_NO_SELF_ASSIGN_ID + 1usize; const ESLINT_NO_SEQUENCES_ID: usize = ESLINT_NO_SELF_COMPARE_ID + 1usize; const ESLINT_NO_SETTER_RETURN_ID: usize = ESLINT_NO_SEQUENCES_ID + 1usize; -const ESLINT_NO_SHADOW_RESTRICTED_NAMES_ID: usize = ESLINT_NO_SETTER_RETURN_ID + 1usize; +const ESLINT_NO_SHADOW_ID: usize = ESLINT_NO_SETTER_RETURN_ID + 1usize; +const ESLINT_NO_SHADOW_RESTRICTED_NAMES_ID: usize = ESLINT_NO_SHADOW_ID + 1usize; const ESLINT_NO_SPARSE_ARRAYS_ID: usize = ESLINT_NO_SHADOW_RESTRICTED_NAMES_ID + 1usize; const ESLINT_NO_TEMPLATE_CURLY_IN_STRING_ID: usize = ESLINT_NO_SPARSE_ARRAYS_ID + 1usize; const ESLINT_NO_TERNARY_ID: usize = ESLINT_NO_TEMPLATE_CURLY_IN_STRING_ID + 1usize; @@ -1636,7 +1641,8 @@ const TYPESCRIPT_NO_REDUNDANT_TYPE_CONSTITUENTS_ID: usize = const TYPESCRIPT_NO_REQUIRE_IMPORTS_ID: usize = TYPESCRIPT_NO_REDUNDANT_TYPE_CONSTITUENTS_ID + 1usize; const TYPESCRIPT_NO_RESTRICTED_TYPES_ID: usize = TYPESCRIPT_NO_REQUIRE_IMPORTS_ID + 1usize; -const TYPESCRIPT_NO_THIS_ALIAS_ID: usize = TYPESCRIPT_NO_RESTRICTED_TYPES_ID + 1usize; +const TYPESCRIPT_NO_SHADOW_ID: usize = TYPESCRIPT_NO_RESTRICTED_TYPES_ID + 1usize; +const TYPESCRIPT_NO_THIS_ALIAS_ID: usize = TYPESCRIPT_NO_SHADOW_ID + 1usize; const TYPESCRIPT_NO_UNNECESSARY_BOOLEAN_LITERAL_COMPARE_ID: usize = TYPESCRIPT_NO_THIS_ALIAS_ID + 1usize; const TYPESCRIPT_NO_UNNECESSARY_CONDITION_ID: usize = @@ -2278,6 +2284,7 @@ impl RuleEnum { Self::EslintNoSelfCompare(_) => ESLINT_NO_SELF_COMPARE_ID, Self::EslintNoSequences(_) => ESLINT_NO_SEQUENCES_ID, Self::EslintNoSetterReturn(_) => ESLINT_NO_SETTER_RETURN_ID, + Self::EslintNoShadow(_) => ESLINT_NO_SHADOW_ID, Self::EslintNoShadowRestrictedNames(_) => ESLINT_NO_SHADOW_RESTRICTED_NAMES_ID, Self::EslintNoSparseArrays(_) => ESLINT_NO_SPARSE_ARRAYS_ID, Self::EslintNoTemplateCurlyInString(_) => ESLINT_NO_TEMPLATE_CURLY_IN_STRING_ID, @@ -2408,6 +2415,7 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(_) => TYPESCRIPT_NO_REQUIRE_IMPORTS_ID, Self::TypescriptNoRestrictedTypes(_) => TYPESCRIPT_NO_RESTRICTED_TYPES_ID, + Self::TypescriptNoShadow(_) => TYPESCRIPT_NO_SHADOW_ID, Self::TypescriptNoThisAlias(_) => TYPESCRIPT_NO_THIS_ALIAS_ID, Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { TYPESCRIPT_NO_UNNECESSARY_BOOLEAN_LITERAL_COMPARE_ID @@ -3054,6 +3062,7 @@ impl RuleEnum { Self::EslintNoSelfCompare(_) => EslintNoSelfCompare::NAME, Self::EslintNoSequences(_) => EslintNoSequences::NAME, Self::EslintNoSetterReturn(_) => EslintNoSetterReturn::NAME, + Self::EslintNoShadow(_) => EslintNoShadow::NAME, Self::EslintNoShadowRestrictedNames(_) => EslintNoShadowRestrictedNames::NAME, Self::EslintNoSparseArrays(_) => EslintNoSparseArrays::NAME, Self::EslintNoTemplateCurlyInString(_) => EslintNoTemplateCurlyInString::NAME, @@ -3184,6 +3193,7 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(_) => TypescriptNoRequireImports::NAME, Self::TypescriptNoRestrictedTypes(_) => TypescriptNoRestrictedTypes::NAME, + Self::TypescriptNoShadow(_) => TypescriptNoShadow::NAME, Self::TypescriptNoThisAlias(_) => TypescriptNoThisAlias::NAME, Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { TypescriptNoUnnecessaryBooleanLiteralCompare::NAME @@ -3822,6 +3832,7 @@ impl RuleEnum { Self::EslintNoSelfCompare(_) => EslintNoSelfCompare::CATEGORY, Self::EslintNoSequences(_) => EslintNoSequences::CATEGORY, Self::EslintNoSetterReturn(_) => EslintNoSetterReturn::CATEGORY, + Self::EslintNoShadow(_) => EslintNoShadow::CATEGORY, Self::EslintNoShadowRestrictedNames(_) => EslintNoShadowRestrictedNames::CATEGORY, Self::EslintNoSparseArrays(_) => EslintNoSparseArrays::CATEGORY, Self::EslintNoTemplateCurlyInString(_) => EslintNoTemplateCurlyInString::CATEGORY, @@ -3962,6 +3973,7 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(_) => TypescriptNoRequireImports::CATEGORY, Self::TypescriptNoRestrictedTypes(_) => TypescriptNoRestrictedTypes::CATEGORY, + Self::TypescriptNoShadow(_) => TypescriptNoShadow::CATEGORY, Self::TypescriptNoThisAlias(_) => TypescriptNoThisAlias::CATEGORY, Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { TypescriptNoUnnecessaryBooleanLiteralCompare::CATEGORY @@ -4633,6 +4645,7 @@ impl RuleEnum { Self::EslintNoSelfCompare(_) => EslintNoSelfCompare::FIX, Self::EslintNoSequences(_) => EslintNoSequences::FIX, Self::EslintNoSetterReturn(_) => EslintNoSetterReturn::FIX, + Self::EslintNoShadow(_) => EslintNoShadow::FIX, Self::EslintNoShadowRestrictedNames(_) => EslintNoShadowRestrictedNames::FIX, Self::EslintNoSparseArrays(_) => EslintNoSparseArrays::FIX, Self::EslintNoTemplateCurlyInString(_) => EslintNoTemplateCurlyInString::FIX, @@ -4763,6 +4776,7 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(_) => TypescriptNoRequireImports::FIX, Self::TypescriptNoRestrictedTypes(_) => TypescriptNoRestrictedTypes::FIX, + Self::TypescriptNoShadow(_) => TypescriptNoShadow::FIX, Self::TypescriptNoThisAlias(_) => TypescriptNoThisAlias::FIX, Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { TypescriptNoUnnecessaryBooleanLiteralCompare::FIX @@ -5416,6 +5430,7 @@ impl RuleEnum { Self::EslintNoSelfCompare(_) => EslintNoSelfCompare::documentation(), Self::EslintNoSequences(_) => EslintNoSequences::documentation(), Self::EslintNoSetterReturn(_) => EslintNoSetterReturn::documentation(), + Self::EslintNoShadow(_) => EslintNoShadow::documentation(), Self::EslintNoShadowRestrictedNames(_) => { EslintNoShadowRestrictedNames::documentation() } @@ -5570,6 +5585,7 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(_) => TypescriptNoRequireImports::documentation(), Self::TypescriptNoRestrictedTypes(_) => TypescriptNoRestrictedTypes::documentation(), + Self::TypescriptNoShadow(_) => TypescriptNoShadow::documentation(), Self::TypescriptNoThisAlias(_) => TypescriptNoThisAlias::documentation(), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { TypescriptNoUnnecessaryBooleanLiteralCompare::documentation() @@ -6569,6 +6585,8 @@ impl RuleEnum { .or_else(|| EslintNoSequences::schema(generator)), Self::EslintNoSetterReturn(_) => EslintNoSetterReturn::config_schema(generator) .or_else(|| EslintNoSetterReturn::schema(generator)), + Self::EslintNoShadow(_) => EslintNoShadow::config_schema(generator) + .or_else(|| EslintNoShadow::schema(generator)), Self::EslintNoShadowRestrictedNames(_) => { EslintNoShadowRestrictedNames::config_schema(generator) .or_else(|| EslintNoShadowRestrictedNames::schema(generator)) @@ -6873,6 +6891,8 @@ impl RuleEnum { TypescriptNoRestrictedTypes::config_schema(generator) .or_else(|| TypescriptNoRestrictedTypes::schema(generator)) } + Self::TypescriptNoShadow(_) => TypescriptNoShadow::config_schema(generator) + .or_else(|| TypescriptNoShadow::schema(generator)), Self::TypescriptNoThisAlias(_) => TypescriptNoThisAlias::config_schema(generator) .or_else(|| TypescriptNoThisAlias::schema(generator)), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { @@ -8275,6 +8295,7 @@ impl RuleEnum { Self::EslintNoSelfCompare(_) => "eslint", Self::EslintNoSequences(_) => "eslint", Self::EslintNoSetterReturn(_) => "eslint", + Self::EslintNoShadow(_) => "eslint", Self::EslintNoShadowRestrictedNames(_) => "eslint", Self::EslintNoSparseArrays(_) => "eslint", Self::EslintNoTemplateCurlyInString(_) => "eslint", @@ -8377,6 +8398,7 @@ impl RuleEnum { Self::TypescriptNoRedundantTypeConstituents(_) => "typescript", Self::TypescriptNoRequireImports(_) => "typescript", Self::TypescriptNoRestrictedTypes(_) => "typescript", + Self::TypescriptNoShadow(_) => "typescript", Self::TypescriptNoThisAlias(_) => "typescript", Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => "typescript", Self::TypescriptNoUnnecessaryCondition(_) => "typescript", @@ -9240,6 +9262,9 @@ impl RuleEnum { Self::EslintNoSetterReturn(_) => { Ok(Self::EslintNoSetterReturn(EslintNoSetterReturn::from_configuration(value)?)) } + Self::EslintNoShadow(_) => { + Ok(Self::EslintNoShadow(EslintNoShadow::from_configuration(value)?)) + } Self::EslintNoShadowRestrictedNames(_) => Ok(Self::EslintNoShadowRestrictedNames( EslintNoShadowRestrictedNames::from_configuration(value)?, )), @@ -9578,6 +9603,9 @@ impl RuleEnum { Self::TypescriptNoRestrictedTypes(_) => Ok(Self::TypescriptNoRestrictedTypes( TypescriptNoRestrictedTypes::from_configuration(value)?, )), + Self::TypescriptNoShadow(_) => { + Ok(Self::TypescriptNoShadow(TypescriptNoShadow::from_configuration(value)?)) + } Self::TypescriptNoThisAlias(_) => { Ok(Self::TypescriptNoThisAlias(TypescriptNoThisAlias::from_configuration(value)?)) } @@ -11122,6 +11150,7 @@ impl RuleEnum { Self::EslintNoSelfCompare(rule) => rule.to_configuration(), Self::EslintNoSequences(rule) => rule.to_configuration(), Self::EslintNoSetterReturn(rule) => rule.to_configuration(), + Self::EslintNoShadow(rule) => rule.to_configuration(), Self::EslintNoShadowRestrictedNames(rule) => rule.to_configuration(), Self::EslintNoSparseArrays(rule) => rule.to_configuration(), Self::EslintNoTemplateCurlyInString(rule) => rule.to_configuration(), @@ -11224,6 +11253,7 @@ impl RuleEnum { Self::TypescriptNoRedundantTypeConstituents(rule) => rule.to_configuration(), Self::TypescriptNoRequireImports(rule) => rule.to_configuration(), Self::TypescriptNoRestrictedTypes(rule) => rule.to_configuration(), + Self::TypescriptNoShadow(rule) => rule.to_configuration(), Self::TypescriptNoThisAlias(rule) => rule.to_configuration(), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(rule) => rule.to_configuration(), Self::TypescriptNoUnnecessaryCondition(rule) => rule.to_configuration(), @@ -11806,6 +11836,7 @@ impl RuleEnum { Self::EslintNoSelfCompare(rule) => rule.run(node, ctx), Self::EslintNoSequences(rule) => rule.run(node, ctx), Self::EslintNoSetterReturn(rule) => rule.run(node, ctx), + Self::EslintNoShadow(rule) => rule.run(node, ctx), Self::EslintNoShadowRestrictedNames(rule) => rule.run(node, ctx), Self::EslintNoSparseArrays(rule) => rule.run(node, ctx), Self::EslintNoTemplateCurlyInString(rule) => rule.run(node, ctx), @@ -11908,6 +11939,7 @@ impl RuleEnum { Self::TypescriptNoRedundantTypeConstituents(rule) => rule.run(node, ctx), Self::TypescriptNoRequireImports(rule) => rule.run(node, ctx), Self::TypescriptNoRestrictedTypes(rule) => rule.run(node, ctx), + Self::TypescriptNoShadow(rule) => rule.run(node, ctx), Self::TypescriptNoThisAlias(rule) => rule.run(node, ctx), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(rule) => rule.run(node, ctx), Self::TypescriptNoUnnecessaryCondition(rule) => rule.run(node, ctx), @@ -12486,6 +12518,7 @@ impl RuleEnum { Self::EslintNoSelfCompare(rule) => rule.run_once(ctx), Self::EslintNoSequences(rule) => rule.run_once(ctx), Self::EslintNoSetterReturn(rule) => rule.run_once(ctx), + Self::EslintNoShadow(rule) => rule.run_once(ctx), Self::EslintNoShadowRestrictedNames(rule) => rule.run_once(ctx), Self::EslintNoSparseArrays(rule) => rule.run_once(ctx), Self::EslintNoTemplateCurlyInString(rule) => rule.run_once(ctx), @@ -12588,6 +12621,7 @@ impl RuleEnum { Self::TypescriptNoRedundantTypeConstituents(rule) => rule.run_once(ctx), Self::TypescriptNoRequireImports(rule) => rule.run_once(ctx), Self::TypescriptNoRestrictedTypes(rule) => rule.run_once(ctx), + Self::TypescriptNoShadow(rule) => rule.run_once(ctx), Self::TypescriptNoThisAlias(rule) => rule.run_once(ctx), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(rule) => rule.run_once(ctx), Self::TypescriptNoUnnecessaryCondition(rule) => rule.run_once(ctx), @@ -13170,6 +13204,7 @@ impl RuleEnum { Self::EslintNoSelfCompare(rule) => rule.run_on_jest_node(jest_node, ctx), Self::EslintNoSequences(rule) => rule.run_on_jest_node(jest_node, ctx), Self::EslintNoSetterReturn(rule) => rule.run_on_jest_node(jest_node, ctx), + Self::EslintNoShadow(rule) => rule.run_on_jest_node(jest_node, ctx), Self::EslintNoShadowRestrictedNames(rule) => rule.run_on_jest_node(jest_node, ctx), Self::EslintNoSparseArrays(rule) => rule.run_on_jest_node(jest_node, ctx), Self::EslintNoTemplateCurlyInString(rule) => rule.run_on_jest_node(jest_node, ctx), @@ -13300,6 +13335,7 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(rule) => rule.run_on_jest_node(jest_node, ctx), Self::TypescriptNoRestrictedTypes(rule) => rule.run_on_jest_node(jest_node, ctx), + Self::TypescriptNoShadow(rule) => rule.run_on_jest_node(jest_node, ctx), Self::TypescriptNoThisAlias(rule) => rule.run_on_jest_node(jest_node, ctx), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(rule) => { rule.run_on_jest_node(jest_node, ctx) @@ -13936,6 +13972,7 @@ impl RuleEnum { Self::EslintNoSelfCompare(rule) => rule.should_run(ctx), Self::EslintNoSequences(rule) => rule.should_run(ctx), Self::EslintNoSetterReturn(rule) => rule.should_run(ctx), + Self::EslintNoShadow(rule) => rule.should_run(ctx), Self::EslintNoShadowRestrictedNames(rule) => rule.should_run(ctx), Self::EslintNoSparseArrays(rule) => rule.should_run(ctx), Self::EslintNoTemplateCurlyInString(rule) => rule.should_run(ctx), @@ -14038,6 +14075,7 @@ impl RuleEnum { Self::TypescriptNoRedundantTypeConstituents(rule) => rule.should_run(ctx), Self::TypescriptNoRequireImports(rule) => rule.should_run(ctx), Self::TypescriptNoRestrictedTypes(rule) => rule.should_run(ctx), + Self::TypescriptNoShadow(rule) => rule.should_run(ctx), Self::TypescriptNoThisAlias(rule) => rule.should_run(ctx), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(rule) => rule.should_run(ctx), Self::TypescriptNoUnnecessaryCondition(rule) => rule.should_run(ctx), @@ -14632,6 +14670,7 @@ impl RuleEnum { Self::EslintNoSelfCompare(_) => EslintNoSelfCompare::IS_TSGOLINT_RULE, Self::EslintNoSequences(_) => EslintNoSequences::IS_TSGOLINT_RULE, Self::EslintNoSetterReturn(_) => EslintNoSetterReturn::IS_TSGOLINT_RULE, + Self::EslintNoShadow(_) => EslintNoShadow::IS_TSGOLINT_RULE, Self::EslintNoShadowRestrictedNames(_) => { EslintNoShadowRestrictedNames::IS_TSGOLINT_RULE } @@ -14786,6 +14825,7 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(_) => TypescriptNoRequireImports::IS_TSGOLINT_RULE, Self::TypescriptNoRestrictedTypes(_) => TypescriptNoRestrictedTypes::IS_TSGOLINT_RULE, + Self::TypescriptNoShadow(_) => TypescriptNoShadow::IS_TSGOLINT_RULE, Self::TypescriptNoThisAlias(_) => TypescriptNoThisAlias::IS_TSGOLINT_RULE, Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { TypescriptNoUnnecessaryBooleanLiteralCompare::IS_TSGOLINT_RULE @@ -15579,6 +15619,7 @@ impl RuleEnum { Self::EslintNoSelfCompare(_) => EslintNoSelfCompare::HAS_CONFIG, Self::EslintNoSequences(_) => EslintNoSequences::HAS_CONFIG, Self::EslintNoSetterReturn(_) => EslintNoSetterReturn::HAS_CONFIG, + Self::EslintNoShadow(_) => EslintNoShadow::HAS_CONFIG, Self::EslintNoShadowRestrictedNames(_) => EslintNoShadowRestrictedNames::HAS_CONFIG, Self::EslintNoSparseArrays(_) => EslintNoSparseArrays::HAS_CONFIG, Self::EslintNoTemplateCurlyInString(_) => EslintNoTemplateCurlyInString::HAS_CONFIG, @@ -15719,6 +15760,7 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(_) => TypescriptNoRequireImports::HAS_CONFIG, Self::TypescriptNoRestrictedTypes(_) => TypescriptNoRestrictedTypes::HAS_CONFIG, + Self::TypescriptNoShadow(_) => TypescriptNoShadow::HAS_CONFIG, Self::TypescriptNoThisAlias(_) => TypescriptNoThisAlias::HAS_CONFIG, Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { TypescriptNoUnnecessaryBooleanLiteralCompare::HAS_CONFIG @@ -16409,6 +16451,7 @@ impl RuleEnum { Self::EslintNoSelfCompare(rule) => rule.types_info(), Self::EslintNoSequences(rule) => rule.types_info(), Self::EslintNoSetterReturn(rule) => rule.types_info(), + Self::EslintNoShadow(rule) => rule.types_info(), Self::EslintNoShadowRestrictedNames(rule) => rule.types_info(), Self::EslintNoSparseArrays(rule) => rule.types_info(), Self::EslintNoTemplateCurlyInString(rule) => rule.types_info(), @@ -16511,6 +16554,7 @@ impl RuleEnum { Self::TypescriptNoRedundantTypeConstituents(rule) => rule.types_info(), Self::TypescriptNoRequireImports(rule) => rule.types_info(), Self::TypescriptNoRestrictedTypes(rule) => rule.types_info(), + Self::TypescriptNoShadow(rule) => rule.types_info(), Self::TypescriptNoThisAlias(rule) => rule.types_info(), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(rule) => rule.types_info(), Self::TypescriptNoUnnecessaryCondition(rule) => rule.types_info(), @@ -17089,6 +17133,7 @@ impl RuleEnum { Self::EslintNoSelfCompare(rule) => rule.run_info(), Self::EslintNoSequences(rule) => rule.run_info(), Self::EslintNoSetterReturn(rule) => rule.run_info(), + Self::EslintNoShadow(rule) => rule.run_info(), Self::EslintNoShadowRestrictedNames(rule) => rule.run_info(), Self::EslintNoSparseArrays(rule) => rule.run_info(), Self::EslintNoTemplateCurlyInString(rule) => rule.run_info(), @@ -17191,6 +17236,7 @@ impl RuleEnum { Self::TypescriptNoRedundantTypeConstituents(rule) => rule.run_info(), Self::TypescriptNoRequireImports(rule) => rule.run_info(), Self::TypescriptNoRestrictedTypes(rule) => rule.run_info(), + Self::TypescriptNoShadow(rule) => rule.run_info(), Self::TypescriptNoThisAlias(rule) => rule.run_info(), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(rule) => rule.run_info(), Self::TypescriptNoUnnecessaryCondition(rule) => rule.run_info(), @@ -17791,6 +17837,7 @@ pub static RULES: std::sync::LazyLock> = std::sync::LazyLock::new( RuleEnum::EslintNoSelfCompare(EslintNoSelfCompare::default()), RuleEnum::EslintNoSequences(EslintNoSequences::default()), RuleEnum::EslintNoSetterReturn(EslintNoSetterReturn::default()), + RuleEnum::EslintNoShadow(EslintNoShadow::default()), RuleEnum::EslintNoShadowRestrictedNames(EslintNoShadowRestrictedNames::default()), RuleEnum::EslintNoSparseArrays(EslintNoSparseArrays::default()), RuleEnum::EslintNoTemplateCurlyInString(EslintNoTemplateCurlyInString::default()), @@ -17921,6 +17968,7 @@ pub static RULES: std::sync::LazyLock> = std::sync::LazyLock::new( ), RuleEnum::TypescriptNoRequireImports(TypescriptNoRequireImports::default()), RuleEnum::TypescriptNoRestrictedTypes(TypescriptNoRestrictedTypes::default()), + RuleEnum::TypescriptNoShadow(TypescriptNoShadow::default()), RuleEnum::TypescriptNoThisAlias(TypescriptNoThisAlias::default()), RuleEnum::TypescriptNoUnnecessaryBooleanLiteralCompare( TypescriptNoUnnecessaryBooleanLiteralCompare::default(), diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index f898dda3db28e..140ca978343c7 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -151,6 +151,7 @@ pub(crate) mod eslint { pub mod no_self_compare; pub mod no_sequences; pub mod no_setter_return; + pub mod no_shadow; pub mod no_shadow_restricted_names; pub mod no_sparse_arrays; pub mod no_template_curly_in_string; @@ -256,6 +257,7 @@ pub(crate) mod typescript { pub mod no_redundant_type_constituents; pub mod no_require_imports; pub mod no_restricted_types; + pub mod no_shadow; pub mod no_this_alias; pub mod no_unnecessary_boolean_literal_compare; pub mod no_unnecessary_condition; diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow.rs b/crates/oxc_linter/src/rules/eslint/no_shadow.rs new file mode 100644 index 0000000000000..c896f43facf59 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_shadow.rs @@ -0,0 +1,211 @@ +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{CompactStr, Span}; +use oxc_syntax::symbol::SymbolFlags; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::{ + context::LintContext, + rule::{DefaultRuleConfig, Rule}, +}; + +fn no_shadow_diagnostic(span: Span, name: &str, shadowed_span: Span) -> OxcDiagnostic { + 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"), + ]) +} + +/// Controls how hoisting is handled when checking for shadowing. +#[derive(Debug, Clone, Default, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum HoistOption { + /// Report shadowing even before the outer variable is declared (due to hoisting). + All, + /// Only report shadowing for function declarations that are hoisted. + #[default] + Functions, + /// Never report shadowing before the outer variable is declared. + Never, +} + +#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", default, deny_unknown_fields)] +pub struct NoShadowConfig { + /// Controls how hoisting is handled. + #[serde(default)] + pub hoist: HoistOption, + + /// List of variable names that are allowed to shadow. + #[serde(default)] + pub allow: Vec, +} + +impl Default for NoShadowConfig { + fn default() -> Self { + Self { hoist: HoistOption::default(), allow: Vec::new() } + } +} + +#[derive(Debug, Default, Clone)] +pub struct NoShadow(Box); + +impl std::ops::Deref for NoShadow { + type Target = NoShadowConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallows variable declarations from shadowing variables declared in the outer scope. + /// + /// ### Why is this bad? + /// + /// Shadowing is the process by which a local variable shares the same name as a variable + /// in its containing scope. This can cause confusion, as it may be unclear which variable + /// is being referenced, and can lead to bugs that are difficult to diagnose. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// var x = 1; + /// function foo() { + /// var x = 2; // x shadows the outer x + /// } + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// var x = 1; + /// function foo() { + /// var y = 2; // different name, no shadowing + /// } + /// ``` + NoShadow, + eslint, + suspicious, + config = NoShadowConfig +); + +impl Rule for NoShadow { + fn from_configuration(value: serde_json::Value) -> Result { + serde_json::from_value::>(value) + .map(|c| Self(Box::new(c.into_inner()))) + } + + fn run_once(&self, ctx: &LintContext) { + let scoping = ctx.scoping(); + + for symbol_id in scoping.symbol_ids() { + let symbol_name = scoping.symbol_name(symbol_id); + + // Skip if in allow list + if self.allow.iter().any(|allowed| allowed.as_str() == symbol_name) { + continue; + } + + let symbol_scope = scoping.symbol_scope_id(symbol_id); + let symbol_flags = scoping.symbol_flags(symbol_id); + let symbol_span = scoping.symbol_span(symbol_id); + + // Skip enum members - they don't shadow outer variables + if symbol_flags.contains(SymbolFlags::EnumMember) { + continue; + } + + // Walk parent scopes looking for shadowed variables + for parent_scope in scoping.scope_ancestors(symbol_scope).skip(1) { + if let Some(shadowed_symbol_id) = scoping.get_binding(parent_scope, symbol_name) { + let shadowed_flags = scoping.symbol_flags(shadowed_symbol_id); + let shadowed_span = scoping.symbol_span(shadowed_symbol_id); + + // Check hoisting rules + if !self.check_hoisting(symbol_span, shadowed_span, shadowed_flags) { + continue; + } + + // Report the shadowing + ctx.diagnostic(no_shadow_diagnostic(symbol_span, symbol_name, shadowed_span)); + break; + } + } + } + } +} + +impl NoShadow { + /// Check if shadowing should be reported based on hoisting rules. + pub fn check_hoisting( + &self, + symbol_span: Span, + shadowed_span: Span, + shadowed_flags: SymbolFlags, + ) -> bool { + match self.hoist { + HoistOption::All => true, + HoistOption::Functions => { + // Only report if the shadowed variable is a function or if the symbol + // comes after the shadowed declaration + shadowed_flags.contains(SymbolFlags::Function) + || symbol_span.start >= shadowed_span.start + } + HoistOption::Never => { + // Only report if the symbol comes after the shadowed declaration + symbol_span.start >= shadowed_span.start + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + // Different names - no shadowing + ("var x = 1; function foo() { var y = 2; }", None), + // Same name in different functions - no shadowing + ("function foo(x) { } function bar(x) { }", None), + // Allowed names + ("var x = 1; function foo() { var x = 2; }", Some(serde_json::json!([{ "allow": ["x"] }]))), + // Reassign + ("let x = true; if (x) { x = false; }", Some(serde_json::json!([{ "allow": ["x"] }]))), + ]; + + let fail = vec![ + // Basic shadowing + ("var x = 1; function foo() { var x = 2; }", None), + // Block scope shadowing + ("const x = 1; { const x = 2; }", None), + // Parameter shadowing outer variable + ("var x = 1; function foo(x) { }", None), + // Nested function shadowing + ("function foo() { var x = 1; function bar() { var x = 2; } }", None), + // Arrow function shadowing + ("const x = 1; const foo = () => { const x = 2; };", None), + // Class method shadowing + ("const x = 1; class Foo { method() { const x = 2; } }", None), + // Let shadowing + ("let x = 1; { let x = 2; }", None), + // Catch clause shadowing + ("const e = 1; try { } catch (e) { }", None), + // For loop variable shadowing + ("const i = 1; for (let i = 0; i < 10; i++) { }", None), + // Destructuring shadowing in nested scope + ("const x = 1; { const { x } = { x: 2 }; }", None), + // Array destructuring shadowing in nested scope + ("const x = 1; { const [x] = [2]; }", None), + ]; + + Tester::new(NoShadow::NAME, NoShadow::PLUGIN, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/rules/typescript/no_shadow.rs b/crates/oxc_linter/src/rules/typescript/no_shadow.rs new file mode 100644 index 0000000000000..8a2b836fe16ec --- /dev/null +++ b/crates/oxc_linter/src/rules/typescript/no_shadow.rs @@ -0,0 +1,349 @@ +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{CompactStr, Span}; +use oxc_syntax::symbol::SymbolFlags; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::{ + context::LintContext, + rule::{DefaultRuleConfig, Rule}, +}; + +fn no_shadow_diagnostic(span: Span, name: &str, shadowed_span: Span) -> OxcDiagnostic { + 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"), + ]) +} + +/// Controls how hoisting is handled when checking for shadowing. +#[derive(Debug, Clone, Default, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum HoistOption { + /// Report shadowing even before the outer variable is declared (due to hoisting). + All, + /// Only report shadowing for function declarations that are hoisted. + #[default] + Functions, + /// Never report shadowing before the outer variable is declared. + Never, +} + +#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", default, deny_unknown_fields)] +pub struct NoShadowConfig { + /// Controls how hoisting is handled. + #[serde(default)] + pub hoist: HoistOption, + + /// List of variable names that are allowed to shadow. + #[serde(default)] + pub allow: Vec, + + /// If `true`, ignore when a type and a value have the same name. + /// This is common in TypeScript: `type Foo = ...; const Foo = ...;` + #[serde(default = "default_true")] + pub ignore_type_value_shadow: bool, + + /// If `true`, ignore when a function type parameter shadows a value. + /// Example: `const T = 1; function foo() {}` + #[serde(default = "default_true")] + pub ignore_function_type_parameter_name_value_shadow: bool, +} + +fn default_true() -> bool { + true +} + +impl Default for NoShadowConfig { + fn default() -> Self { + Self { + hoist: HoistOption::default(), + allow: Vec::new(), + ignore_type_value_shadow: true, + ignore_function_type_parameter_name_value_shadow: true, + } + } +} + +#[derive(Debug, Default, Clone)] +pub struct NoShadow(Box); + +impl std::ops::Deref for NoShadow { + type Target = NoShadowConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallows variable declarations from shadowing variables declared in the outer scope. + /// + /// ### Why is this bad? + /// + /// Shadowing is the process by which a local variable shares the same name as a variable + /// in its containing scope. This can cause confusion, as it may be unclear which variable + /// is being referenced, and can lead to bugs that are difficult to diagnose. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```ts + /// const x = 1; + /// function foo() { + /// const x = 2; // x shadows the outer x + /// } + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```ts + /// const x = 1; + /// function foo() { + /// const y = 2; // different name, no shadowing + /// } + /// ``` + NoShadow, + typescript, + suspicious, + config = NoShadowConfig +); + +impl Rule for NoShadow { + fn from_configuration(value: serde_json::Value) -> Result { + serde_json::from_value::>(value) + .map(|c| Self(Box::new(c.into_inner()))) + } + + fn run_once(&self, ctx: &LintContext) { + let scoping = ctx.scoping(); + + for symbol_id in scoping.symbol_ids() { + let symbol_name = scoping.symbol_name(symbol_id); + + // Skip if in allow list + if self.allow.iter().any(|allowed| allowed.as_str() == symbol_name) { + continue; + } + + let symbol_scope = scoping.symbol_scope_id(symbol_id); + let symbol_flags = scoping.symbol_flags(symbol_id); + let symbol_span = scoping.symbol_span(symbol_id); + + // Skip enum members - they don't shadow outer variables + if symbol_flags.contains(SymbolFlags::EnumMember) { + continue; + } + + // Walk parent scopes looking for shadowed variables + for parent_scope in scoping.scope_ancestors(symbol_scope).skip(1) { + if let Some(shadowed_symbol_id) = scoping.get_binding(parent_scope, symbol_name) { + let shadowed_flags = scoping.symbol_flags(shadowed_symbol_id); + let shadowed_span = scoping.symbol_span(shadowed_symbol_id); + + // Check if we should ignore this shadowing based on TypeScript rules + if self.should_ignore_shadow( + ctx, + symbol_id, + symbol_flags, + shadowed_symbol_id, + shadowed_flags, + ) { + break; + } + + // Check hoisting rules + if !self.check_hoisting(symbol_span, shadowed_span, shadowed_flags) { + continue; + } + + // Report the shadowing + ctx.diagnostic(no_shadow_diagnostic(symbol_span, symbol_name, shadowed_span)); + break; + } + } + } + } +} + +impl NoShadow { + /// Check if we should ignore this shadowing based on TypeScript-specific rules. + fn should_ignore_shadow( + &self, + ctx: &LintContext, + symbol_id: oxc_syntax::symbol::SymbolId, + symbol_flags: SymbolFlags, + shadowed_symbol_id: oxc_syntax::symbol::SymbolId, + shadowed_flags: SymbolFlags, + ) -> bool { + // Check type vs value shadowing + if self.ignore_type_value_shadow { + let symbol_is_type = is_type_only(symbol_flags); + let shadowed_is_type = is_type_only(shadowed_flags); + + // If one is a type and the other is a value, ignore + if symbol_is_type != shadowed_is_type { + return true; + } + } + + // Check function type parameter shadowing value + if self.ignore_function_type_parameter_name_value_shadow + && symbol_flags.contains(SymbolFlags::TypeParameter) + { + // Check if the type parameter is in a function context + let declaration_node_id = ctx.scoping().symbol_declaration(symbol_id); + let declaration_node = ctx.nodes().get_node(declaration_node_id); + + // Walk up to find if we're in a function + for ancestor in ctx.nodes().ancestor_ids(declaration_node.id()) { + let ancestor_node = ctx.nodes().get_node(ancestor); + if matches!( + ancestor_node.kind(), + AstKind::Function(_) + | AstKind::ArrowFunctionExpression(_) + | AstKind::TSMethodSignature(_) + | AstKind::TSCallSignatureDeclaration(_) + | AstKind::TSConstructSignatureDeclaration(_) + ) { + // This is a function type parameter, check if shadowed is a value + if !is_type_only(shadowed_flags) { + return true; + } + break; + } + } + } + + // Check if shadowing an import that's only used as a type + if shadowed_flags.contains(SymbolFlags::Import) { + // Check if all references to the import are type-only + let references: Vec<_> = + ctx.scoping().get_resolved_references(shadowed_symbol_id).collect(); + let has_refs = !references.is_empty(); + let all_type_refs = references.iter().all(|r| r.is_type()); + + if has_refs && all_type_refs && !is_type_only(symbol_flags) { + // The import is only used as a type, and we're declaring a value + // This is allowed in TypeScript + return true; + } + } + + false + } + + /// Check if shadowing should be reported based on hoisting rules. + fn check_hoisting( + &self, + symbol_span: Span, + shadowed_span: Span, + shadowed_flags: SymbolFlags, + ) -> bool { + match self.hoist { + HoistOption::All => true, + HoistOption::Functions => { + // Only report if the shadowed variable is a function or if the symbol + // comes after the shadowed declaration + shadowed_flags.contains(SymbolFlags::Function) + || symbol_span.start >= shadowed_span.start + } + HoistOption::Never => { + // Only report if the symbol comes after the shadowed declaration + symbol_span.start >= shadowed_span.start + } + } + } +} + +/// Check if the symbol is a type-only declaration (not a value). +fn is_type_only(flags: SymbolFlags) -> bool { + flags.intersects(SymbolFlags::TypeAlias | SymbolFlags::Interface | SymbolFlags::TypeParameter) + && !flags.intersects( + SymbolFlags::FunctionScopedVariable + | SymbolFlags::BlockScopedVariable + | SymbolFlags::Function + | SymbolFlags::Class + | SymbolFlags::Enum + | SymbolFlags::ConstEnum, + ) +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + // Different names - no shadowing + ("const x = 1; function foo() { const y = 2; }", None), + // Same name in different functions - no shadowing + ("function foo(x) { } function bar(x) { }", None), + // Type vs value with same name (TypeScript) - default ignored + ("type Foo = string; const Foo = 'bar';", None), + // Interface vs value with same name + ("interface Foo { x: number } const Foo = { x: 1 };", None), + // Allowed names + ( + "const x = 1; function foo() { const x = 2; }", + Some(serde_json::json!([{ "allow": ["x"] }])), + ), + // Type parameter shadowing value (default ignored) + ("const T = 1; function foo() { }", None), + // Enum member doesn't shadow + ("const Red = 1; enum Color { Red, Green, Blue }", None), + // Class with same name as type (declaration merging) + ("interface Foo { x: number } class Foo { x = 1; }", None), + // Namespace with same name as class + ("class Foo { } namespace Foo { export const x = 1; }", None), + // Import used only as type, value with same name + ( + r#" + import { Foo } from './foo'; + type X = Foo; + const Foo = 1; + "#, + None, + ), + ]; + + let fail = vec![ + // Basic shadowing + ("const x = 1; function foo() { const x = 2; }", None), + // Block scope shadowing + ("const x = 1; { const x = 2; }", None), + // Parameter shadowing outer variable + ("const x = 1; function foo(x) { }", None), + // Nested function shadowing + ("function foo() { const x = 1; function bar() { const x = 2; } }", None), + // Arrow function shadowing + ("const x = 1; const foo = () => { const x = 2; };", None), + // Class method shadowing + ("const x = 1; class Foo { method() { const x = 2; } }", None), + // Let shadowing + ("let x = 1; { let x = 2; }", None), + // Var shadowing (hoisted) + ("var x = 1; function foo() { var x = 2; }", None), + // Type shadowing type (not ignored) + ("type Foo = string; { type Foo = number; }", None), + // Interface shadowing interface + ("interface Foo { x: number } { interface Foo { y: string } }", None), + // Catch clause shadowing + ("const e = 1; try { } catch (e) { }", None), + // For loop variable shadowing + ("const i = 1; for (let i = 0; i < 10; i++) { }", None), + // Destructuring shadowing in nested scope + ("const x = 1; { const { x } = { x: 2 }; }", None), + // Array destructuring shadowing in nested scope + ("const x = 1; { const [x] = [2]; }", None), + ]; + + Tester::new(NoShadow::NAME, NoShadow::PLUGIN, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/eslint_no_shadow.snap b/crates/oxc_linter/src/snapshots/eslint_no_shadow.snap new file mode 100644 index 0000000000000..59027ef1046d9 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/eslint_no_shadow.snap @@ -0,0 +1,103 @@ +--- +source: crates/oxc_linter/src/tester.rs +assertion_line: 444 +--- + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ var x = 1; function foo() { var x = 2; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const x = 1; { const x = 2; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ var x = 1; function foo(x) { } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:22] + 1 │ function foo() { var x = 1; function bar() { var x = 2; } } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const x = 1; const foo = () => { const x = 2; }; + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const x = 1; class Foo { method() { const x = 2; } } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ let x = 1; { let x = 2; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'e' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const e = 1; try { } catch (e) { } + · ┬ ┬ + · │ ╰── 'e' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'e' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'i' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const i = 1; for (let i = 0; i < 10; i++) { } + · ┬ ┬ + · │ ╰── 'i' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'i' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const x = 1; { const { x } = { x: 2 }; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const x = 1; { const [x] = [2]; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. diff --git a/crates/oxc_linter/src/snapshots/typescript_no_shadow.snap b/crates/oxc_linter/src/snapshots/typescript_no_shadow.snap new file mode 100644 index 0000000000000..594bd24e0e9c2 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/typescript_no_shadow.snap @@ -0,0 +1,130 @@ +--- +source: crates/oxc_linter/src/tester.rs +assertion_line: 444 +--- + + ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const x = 1; function foo() { const x = 2; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const x = 1; { const x = 2; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const x = 1; function foo(x) { } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:24] + 1 │ function foo() { const x = 1; function bar() { const x = 2; } } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const x = 1; const foo = () => { const x = 2; }; + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const x = 1; class Foo { method() { const x = 2; } } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ let x = 1; { let x = 2; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ var x = 1; function foo() { var x = 2; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:6] + 1 │ type Foo = string; { type Foo = number; } + · ─┬─ ─┬─ + · │ ╰── 'Foo' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:11] + 1 │ interface Foo { x: number } { interface Foo { y: string } } + · ─┬─ ─┬─ + · │ ╰── 'Foo' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'e' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const e = 1; try { } catch (e) { } + · ┬ ┬ + · │ ╰── 'e' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'e' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'i' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const i = 1; for (let i = 0; i < 10; i++) { } + · ┬ ┬ + · │ ╰── 'i' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'i' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const x = 1; { const { x } = { x: 2 }; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const x = 1; { const [x] = [2]; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. From e7999f72720aa14ce52e9331d1932dabf9dd68d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20Gabriel?= Date: Thu, 5 Feb 2026 11:55:41 +0100 Subject: [PATCH 02/15] test(oxlint): options --- .../oxc_linter/src/rules/eslint/no_shadow.rs | 61 ++++++++++ .../src/rules/typescript/no_shadow.rs | 107 ++++++++++++++++++ .../src/snapshots/eslint_no_shadow.snap | 73 +++++++++++- .../src/snapshots/typescript_no_shadow.snap | 64 ++++++++++- 4 files changed, 303 insertions(+), 2 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow.rs b/crates/oxc_linter/src/rules/eslint/no_shadow.rs index c896f43facf59..a3287154f5408 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow.rs @@ -180,6 +180,42 @@ fn test() { ("var x = 1; function foo() { var x = 2; }", Some(serde_json::json!([{ "allow": ["x"] }]))), // Reassign ("let x = true; if (x) { x = false; }", Some(serde_json::json!([{ "allow": ["x"] }]))), + // --- hoist = never: do NOT report if the outer declaration happens later --- + ( + "function f() { { let x = 1; } let x = 2; }", + Some(serde_json::json!([{ "hoist": "never" }])), + ), + // hoist = never: even if the outer is a function declaration, "never" should NOT report when it appears later + ( + "function f() { { let x = 1; } function x() {} }", + Some(serde_json::json!([{ "hoist": "never" }])), + ), + // --- hoist = functions: do NOT report if the outer declaration happens later and it is NOT a function declaration --- + ( + "function f() { { let x = 1; } var x = 2; }", + Some(serde_json::json!([{ "hoist": "functions" }])), + ), + ( + "function f() { { let C = 1; } class C {} }", + Some(serde_json::json!([{ "hoist": "functions" }])), + ), + // --- allow: should suppress the diagnostic regardless of hoist setting --- + ( + "function f() { { let x = 1; } let x = 2; }", + Some(serde_json::json!([{ "hoist": "all", "allow": ["x"] }])), + ), + // allow multiple names + ( + "let x = 1; function f(){ let x = 2; } let y = 1; function g(){ let y = 2; }", + Some(serde_json::json!([{ "allow": ["x", "y"] }])), + ), + // allow applied to destructuring (you already have the failing version; this ensures the escape hatch works) + ("const x = 1; { const { x } = { x: 2 }; }", Some(serde_json::json!([{ "allow": ["x"] }]))), + // Outer is NOT a function declaration; it's a const variable initialized with a function expression. + ( + "function f() { { let x = 1; } const x = function() {}; }", + Some(serde_json::json!([{ "hoist": "functions" }])), + ), ]; let fail = vec![ @@ -205,6 +241,31 @@ fn test() { ("const x = 1; { const { x } = { x: 2 }; }", None), // Array destructuring shadowing in nested scope ("const x = 1; { const [x] = [2]; }", None), + ("let x = 1; { { let x = 3; } let x = 2; }", None), + // --- hoist = all: DO report even if the outer declaration happens later --- + ( + "function f() { { let x = 1; } let x = 2; }", + Some(serde_json::json!([{ "hoist": "all" }])), + ), + // --- hoist = functions: DO report if the shadowed symbol is a function declaration even when it appears later --- + ( + "function f() { { let x = 1; } function x() {} }", + Some(serde_json::json!([{ "hoist": "functions" }])), + ), + // hoist = all: should also report when the outer is var and appears later (generalized hoisting behavior) + ( + "function f() { { let x = 1; } var x = 2; }", + Some(serde_json::json!([{ "hoist": "all" }])), + ), + // hoist = never: should still report the normal case (outer first, inner later) + ( + "function f() { let x = 2; { let x = 1; } }", + Some(serde_json::json!([{ "hoist": "never" }])), + ), + // --- allow: allowing only "x" must NOT allow "y" --- + ("let y = 1; function g(){ let y = 2; }", Some(serde_json::json!([{ "allow": ["x"] }]))), + // allow is case-sensitive + ("let x = 1; function f(){ let x = 2; }", Some(serde_json::json!([{ "allow": ["X"] }]))), ]; Tester::new(NoShadow::NAME, NoShadow::PLUGIN, pass, fail).test_and_snapshot(); diff --git a/crates/oxc_linter/src/rules/typescript/no_shadow.rs b/crates/oxc_linter/src/rules/typescript/no_shadow.rs index 8a2b836fe16ec..e141335196353 100644 --- a/crates/oxc_linter/src/rules/typescript/no_shadow.rs +++ b/crates/oxc_linter/src/rules/typescript/no_shadow.rs @@ -312,6 +312,66 @@ fn test() { "#, None, ), + // ------------------------- + // allow + // ------------------------- + // `allow` should win even when it would normally be a failure. + ( + "const x = 1; function foo() { const x = 2; }", + Some(serde_json::json!([{ "allow": ["x"] }])), + ), + // `allow` should also override type/value behavior even when ignoreTypeValueShadow=false. + ( + "type Foo = string; const Foo = 'bar';", + Some(serde_json::json!([{ + "ignoreTypeValueShadow": false, + "allow": ["Foo"] + }])), + ), + // ------------------------- + // ignoreTypeValueShadow + // ------------------------- + // Default (true): type vs value with the same name should be ignored. + ("type Foo = string; const Foo = 'bar';", None), + // Same idea for interface vs value (declaration merging is common in TS). + ("interface Foo { x: number } const Foo = { x: 1 };", None), + // ------------------------- + // ignoreFunctionTypeParameterNameValueShadow + // (interaction with ignoreTypeValueShadow) + // ------------------------- + // If ignoreTypeValueShadow=false, this would normally be reportable... + // ...but with ignoreFunctionTypeParameterNameValueShadow=true it must be ignored (pass). + ( + "const T = 1; function foo() { }", + Some(serde_json::json!([{ + "ignoreTypeValueShadow": false, + "ignoreFunctionTypeParameterNameValueShadow": true + }])), + ), + // ------------------------- + // hoist + // ------------------------- + // Outer symbol is `var` (hoisted like var), but declared *after* the inner declaration. + // With hoist=functions or hoist=never, do NOT report shadowing before the outer declaration. + ( + "function outer() { function inner() { const x = 1; } var x = 2; }", + Some(serde_json::json!([{ "hoist": "functions" }])), // default, explicit + ), + ( + "function outer() { function inner() { const x = 1; } var x = 2; }", + Some(serde_json::json!([{ "hoist": "never" }])), + ), + // hoist=never: even if the outer symbol is a function declaration declared later, + // do NOT report it (because we are not considering hoisting). + ( + "function outer() { function inner() { const foo = 1; } function foo() {} }", + Some(serde_json::json!([{ "hoist": "never" }])), + ), + // allow + hoist: even if hoist=all would normally fail, `allow` should still allow it. + ( + "function outer() { function inner() { const x = 1; } var x = 2; }", + Some(serde_json::json!([{ "hoist": "all", "allow": ["x"] }])), + ), ]; let fail = vec![ @@ -343,6 +403,53 @@ fn test() { ("const x = 1; { const { x } = { x: 2 }; }", None), // Array destructuring shadowing in nested scope ("const x = 1; { const [x] = [2]; }", None), + // ------------------------- + // ignoreTypeValueShadow = false => type/value with the same name is now reportable + // ------------------------- + ( + "type Foo = string; { const Foo = 'bar'; }", + Some(serde_json::json!([{ "ignoreTypeValueShadow": false }])), + ), + ( + "interface Foo { x: number }; { const Foo = { x: 1 } };", + Some(serde_json::json!([{ "ignoreTypeValueShadow": false }])), + ), + // ------------------------- + // ignoreFunctionTypeParameterNameValueShadow = false + // (and ignoreTypeValueShadow=false to ensure this option is the one deciding) + // ------------------------- + // Now the function type parameter shadowing a value should be reported. + ( + "const T = 1; function foo() { }", + Some(serde_json::json!([{ + "ignoreTypeValueShadow": false, + "ignoreFunctionTypeParameterNameValueShadow": false + }])), + ), + // ------------------------- + // hoist + // ------------------------- + // Outer `var` declared after: with hoist=all it SHOULD be reported + // (because we consider hoisting for all declarations). + ( + "function outer() { function inner() { const x = 1; } var x = 2; }", + Some(serde_json::json!([{ "hoist": "all" }])), + ), + // Outer `function` declared after: with hoist=functions it SHOULD be reported. + ( + "function outer() { function inner() { const foo = 1; } function foo() {} }", + Some(serde_json::json!([{ "hoist": "functions" }])), + ), + // And with hoist=all it should also be reported. + ( + "function outer() { function inner() { const foo = 1; } function foo() {} }", + Some(serde_json::json!([{ "hoist": "all" }])), + ), + // Sanity check: a "normal" shadow (outer declared before) should fail regardless of hoist. + ( + "function outer() { var x = 2; function inner() { const x = 1; } }", + Some(serde_json::json!([{ "hoist": "never" }])), + ), ]; Tester::new(NoShadow::NAME, NoShadow::PLUGIN, pass, fail).test_and_snapshot(); diff --git a/crates/oxc_linter/src/snapshots/eslint_no_shadow.snap b/crates/oxc_linter/src/snapshots/eslint_no_shadow.snap index 59027ef1046d9..0d6ec68288eb1 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_shadow.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_shadow.snap @@ -1,6 +1,5 @@ --- source: crates/oxc_linter/src/tester.rs -assertion_line: 444 --- ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. @@ -101,3 +100,75 @@ assertion_line: 444 · ╰── shadowed declaration is here ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ let x = 1; { { let x = 3; } let x = 2; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ let x = 1; { { let x = 3; } let x = 2; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:22] + 1 │ function f() { { let x = 1; } let x = 2; } + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'x' is declared here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:22] + 1 │ function f() { { let x = 1; } function x() {} } + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'x' is declared here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:22] + 1 │ function f() { { let x = 1; } var x = 2; } + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'x' is declared here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:20] + 1 │ function f() { let x = 2; { let x = 1; } } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'y' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ let y = 1; function g(){ let y = 2; } + · ┬ ┬ + · │ ╰── 'y' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'y' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ let x = 1; function f(){ let x = 2; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. diff --git a/crates/oxc_linter/src/snapshots/typescript_no_shadow.snap b/crates/oxc_linter/src/snapshots/typescript_no_shadow.snap index 594bd24e0e9c2..484610b51b370 100644 --- a/crates/oxc_linter/src/snapshots/typescript_no_shadow.snap +++ b/crates/oxc_linter/src/snapshots/typescript_no_shadow.snap @@ -1,6 +1,5 @@ --- source: crates/oxc_linter/src/tester.rs -assertion_line: 444 --- ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. @@ -128,3 +127,66 @@ assertion_line: 444 · ╰── shadowed declaration is here ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:6] + 1 │ type Foo = string; { const Foo = 'bar'; } + · ─┬─ ─┬─ + · │ ╰── 'Foo' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:11] + 1 │ interface Foo { x: number }; { const Foo = { x: 1 } }; + · ─┬─ ─┬─ + · │ ╰── 'Foo' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'T' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const T = 1; function foo() { } + · ┬ ┬ + · │ ╰── 'T' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'T' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:45] + 1 │ function outer() { function inner() { const x = 1; } var x = 2; } + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'x' is declared here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:45] + 1 │ function outer() { function inner() { const foo = 1; } function foo() {} } + · ─┬─ ─┬─ + · │ ╰── shadowed declaration is here + · ╰── 'foo' is declared here + ╰──── + help: Consider renaming 'foo' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:45] + 1 │ function outer() { function inner() { const foo = 1; } function foo() {} } + · ─┬─ ─┬─ + · │ ╰── shadowed declaration is here + · ╰── 'foo' is declared here + ╰──── + help: Consider renaming 'foo' to avoid shadowing the variable from the outer scope. + + ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:24] + 1 │ function outer() { var x = 2; function inner() { const x = 1; } } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. From c4e5cfe32db4fe1ce464a0cacf37922e114d8a98 Mon Sep 17 00:00:00 2001 From: victor141516 Date: Thu, 5 Feb 2026 14:27:31 +0100 Subject: [PATCH 03/15] refactor(oxlint): use a single rule for no-shadow --- .../src/generated/rule_runner_impls.rs | 5 - crates/oxc_linter/src/generated/rules_enum.rs | 26 +- crates/oxc_linter/src/rules.rs | 1 - .../oxc_linter/src/rules/eslint/no_shadow.rs | 272 ----------- .../src/rules/eslint/no_shadow/diagnostic.rs | 13 + .../src/rules/eslint/no_shadow/mod.rs | 234 +++++++++ .../src/rules/eslint/no_shadow/options.rs | 53 ++ .../src/rules/eslint/no_shadow/tests.rs | 174 +++++++ .../src/rules/typescript/no_shadow.rs | 456 ------------------ .../src/snapshots/eslint_no_shadow.snap | 86 ++++ 10 files changed, 561 insertions(+), 759 deletions(-) delete mode 100644 crates/oxc_linter/src/rules/eslint/no_shadow.rs create mode 100644 crates/oxc_linter/src/rules/eslint/no_shadow/diagnostic.rs create mode 100644 crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs create mode 100644 crates/oxc_linter/src/rules/eslint/no_shadow/options.rs create mode 100644 crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs delete mode 100644 crates/oxc_linter/src/rules/typescript/no_shadow.rs diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index ff97bd30fdc00..74aea63d8cb4d 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -1667,11 +1667,6 @@ impl RuleRunner for crate::rules::typescript::no_restricted_types::NoRestrictedT const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } -impl RuleRunner for crate::rules::typescript::no_shadow::NoShadow { - const NODE_TYPES: Option<&AstTypesBitset> = None; - const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce; -} - impl RuleRunner for crate::rules::typescript::no_this_alias::NoThisAlias { const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[ AstType::AssignmentExpression, diff --git a/crates/oxc_linter/src/generated/rules_enum.rs b/crates/oxc_linter/src/generated/rules_enum.rs index 5abade2c39d69..4adeb384f8029 100644 --- a/crates/oxc_linter/src/generated/rules_enum.rs +++ b/crates/oxc_linter/src/generated/rules_enum.rs @@ -480,7 +480,6 @@ pub use crate::rules::typescript::no_non_null_assertion::NoNonNullAssertion as T pub use crate::rules::typescript::no_redundant_type_constituents::NoRedundantTypeConstituents as TypescriptNoRedundantTypeConstituents; pub use crate::rules::typescript::no_require_imports::NoRequireImports as TypescriptNoRequireImports; pub use crate::rules::typescript::no_restricted_types::NoRestrictedTypes as TypescriptNoRestrictedTypes; -pub use crate::rules::typescript::no_shadow::NoShadow as TypescriptNoShadow; pub use crate::rules::typescript::no_this_alias::NoThisAlias as TypescriptNoThisAlias; pub use crate::rules::typescript::no_unnecessary_boolean_literal_compare::NoUnnecessaryBooleanLiteralCompare as TypescriptNoUnnecessaryBooleanLiteralCompare; pub use crate::rules::typescript::no_unnecessary_condition::NoUnnecessaryCondition as TypescriptNoUnnecessaryCondition; @@ -941,7 +940,6 @@ pub enum RuleEnum { TypescriptNoRedundantTypeConstituents(TypescriptNoRedundantTypeConstituents), TypescriptNoRequireImports(TypescriptNoRequireImports), TypescriptNoRestrictedTypes(TypescriptNoRestrictedTypes), - TypescriptNoShadow(TypescriptNoShadow), TypescriptNoThisAlias(TypescriptNoThisAlias), TypescriptNoUnnecessaryBooleanLiteralCompare(TypescriptNoUnnecessaryBooleanLiteralCompare), TypescriptNoUnnecessaryCondition(TypescriptNoUnnecessaryCondition), @@ -1641,8 +1639,7 @@ const TYPESCRIPT_NO_REDUNDANT_TYPE_CONSTITUENTS_ID: usize = const TYPESCRIPT_NO_REQUIRE_IMPORTS_ID: usize = TYPESCRIPT_NO_REDUNDANT_TYPE_CONSTITUENTS_ID + 1usize; const TYPESCRIPT_NO_RESTRICTED_TYPES_ID: usize = TYPESCRIPT_NO_REQUIRE_IMPORTS_ID + 1usize; -const TYPESCRIPT_NO_SHADOW_ID: usize = TYPESCRIPT_NO_RESTRICTED_TYPES_ID + 1usize; -const TYPESCRIPT_NO_THIS_ALIAS_ID: usize = TYPESCRIPT_NO_SHADOW_ID + 1usize; +const TYPESCRIPT_NO_THIS_ALIAS_ID: usize = TYPESCRIPT_NO_RESTRICTED_TYPES_ID + 1usize; const TYPESCRIPT_NO_UNNECESSARY_BOOLEAN_LITERAL_COMPARE_ID: usize = TYPESCRIPT_NO_THIS_ALIAS_ID + 1usize; const TYPESCRIPT_NO_UNNECESSARY_CONDITION_ID: usize = @@ -2415,7 +2412,6 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(_) => TYPESCRIPT_NO_REQUIRE_IMPORTS_ID, Self::TypescriptNoRestrictedTypes(_) => TYPESCRIPT_NO_RESTRICTED_TYPES_ID, - Self::TypescriptNoShadow(_) => TYPESCRIPT_NO_SHADOW_ID, Self::TypescriptNoThisAlias(_) => TYPESCRIPT_NO_THIS_ALIAS_ID, Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { TYPESCRIPT_NO_UNNECESSARY_BOOLEAN_LITERAL_COMPARE_ID @@ -3193,7 +3189,6 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(_) => TypescriptNoRequireImports::NAME, Self::TypescriptNoRestrictedTypes(_) => TypescriptNoRestrictedTypes::NAME, - Self::TypescriptNoShadow(_) => TypescriptNoShadow::NAME, Self::TypescriptNoThisAlias(_) => TypescriptNoThisAlias::NAME, Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { TypescriptNoUnnecessaryBooleanLiteralCompare::NAME @@ -3973,7 +3968,6 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(_) => TypescriptNoRequireImports::CATEGORY, Self::TypescriptNoRestrictedTypes(_) => TypescriptNoRestrictedTypes::CATEGORY, - Self::TypescriptNoShadow(_) => TypescriptNoShadow::CATEGORY, Self::TypescriptNoThisAlias(_) => TypescriptNoThisAlias::CATEGORY, Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { TypescriptNoUnnecessaryBooleanLiteralCompare::CATEGORY @@ -4776,7 +4770,6 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(_) => TypescriptNoRequireImports::FIX, Self::TypescriptNoRestrictedTypes(_) => TypescriptNoRestrictedTypes::FIX, - Self::TypescriptNoShadow(_) => TypescriptNoShadow::FIX, Self::TypescriptNoThisAlias(_) => TypescriptNoThisAlias::FIX, Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { TypescriptNoUnnecessaryBooleanLiteralCompare::FIX @@ -5585,7 +5578,6 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(_) => TypescriptNoRequireImports::documentation(), Self::TypescriptNoRestrictedTypes(_) => TypescriptNoRestrictedTypes::documentation(), - Self::TypescriptNoShadow(_) => TypescriptNoShadow::documentation(), Self::TypescriptNoThisAlias(_) => TypescriptNoThisAlias::documentation(), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { TypescriptNoUnnecessaryBooleanLiteralCompare::documentation() @@ -6891,8 +6883,6 @@ impl RuleEnum { TypescriptNoRestrictedTypes::config_schema(generator) .or_else(|| TypescriptNoRestrictedTypes::schema(generator)) } - Self::TypescriptNoShadow(_) => TypescriptNoShadow::config_schema(generator) - .or_else(|| TypescriptNoShadow::schema(generator)), Self::TypescriptNoThisAlias(_) => TypescriptNoThisAlias::config_schema(generator) .or_else(|| TypescriptNoThisAlias::schema(generator)), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { @@ -8398,7 +8388,6 @@ impl RuleEnum { Self::TypescriptNoRedundantTypeConstituents(_) => "typescript", Self::TypescriptNoRequireImports(_) => "typescript", Self::TypescriptNoRestrictedTypes(_) => "typescript", - Self::TypescriptNoShadow(_) => "typescript", Self::TypescriptNoThisAlias(_) => "typescript", Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => "typescript", Self::TypescriptNoUnnecessaryCondition(_) => "typescript", @@ -9603,9 +9592,6 @@ impl RuleEnum { Self::TypescriptNoRestrictedTypes(_) => Ok(Self::TypescriptNoRestrictedTypes( TypescriptNoRestrictedTypes::from_configuration(value)?, )), - Self::TypescriptNoShadow(_) => { - Ok(Self::TypescriptNoShadow(TypescriptNoShadow::from_configuration(value)?)) - } Self::TypescriptNoThisAlias(_) => { Ok(Self::TypescriptNoThisAlias(TypescriptNoThisAlias::from_configuration(value)?)) } @@ -11253,7 +11239,6 @@ impl RuleEnum { Self::TypescriptNoRedundantTypeConstituents(rule) => rule.to_configuration(), Self::TypescriptNoRequireImports(rule) => rule.to_configuration(), Self::TypescriptNoRestrictedTypes(rule) => rule.to_configuration(), - Self::TypescriptNoShadow(rule) => rule.to_configuration(), Self::TypescriptNoThisAlias(rule) => rule.to_configuration(), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(rule) => rule.to_configuration(), Self::TypescriptNoUnnecessaryCondition(rule) => rule.to_configuration(), @@ -11939,7 +11924,6 @@ impl RuleEnum { Self::TypescriptNoRedundantTypeConstituents(rule) => rule.run(node, ctx), Self::TypescriptNoRequireImports(rule) => rule.run(node, ctx), Self::TypescriptNoRestrictedTypes(rule) => rule.run(node, ctx), - Self::TypescriptNoShadow(rule) => rule.run(node, ctx), Self::TypescriptNoThisAlias(rule) => rule.run(node, ctx), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(rule) => rule.run(node, ctx), Self::TypescriptNoUnnecessaryCondition(rule) => rule.run(node, ctx), @@ -12621,7 +12605,6 @@ impl RuleEnum { Self::TypescriptNoRedundantTypeConstituents(rule) => rule.run_once(ctx), Self::TypescriptNoRequireImports(rule) => rule.run_once(ctx), Self::TypescriptNoRestrictedTypes(rule) => rule.run_once(ctx), - Self::TypescriptNoShadow(rule) => rule.run_once(ctx), Self::TypescriptNoThisAlias(rule) => rule.run_once(ctx), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(rule) => rule.run_once(ctx), Self::TypescriptNoUnnecessaryCondition(rule) => rule.run_once(ctx), @@ -13335,7 +13318,6 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(rule) => rule.run_on_jest_node(jest_node, ctx), Self::TypescriptNoRestrictedTypes(rule) => rule.run_on_jest_node(jest_node, ctx), - Self::TypescriptNoShadow(rule) => rule.run_on_jest_node(jest_node, ctx), Self::TypescriptNoThisAlias(rule) => rule.run_on_jest_node(jest_node, ctx), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(rule) => { rule.run_on_jest_node(jest_node, ctx) @@ -14075,7 +14057,6 @@ impl RuleEnum { Self::TypescriptNoRedundantTypeConstituents(rule) => rule.should_run(ctx), Self::TypescriptNoRequireImports(rule) => rule.should_run(ctx), Self::TypescriptNoRestrictedTypes(rule) => rule.should_run(ctx), - Self::TypescriptNoShadow(rule) => rule.should_run(ctx), Self::TypescriptNoThisAlias(rule) => rule.should_run(ctx), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(rule) => rule.should_run(ctx), Self::TypescriptNoUnnecessaryCondition(rule) => rule.should_run(ctx), @@ -14825,7 +14806,6 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(_) => TypescriptNoRequireImports::IS_TSGOLINT_RULE, Self::TypescriptNoRestrictedTypes(_) => TypescriptNoRestrictedTypes::IS_TSGOLINT_RULE, - Self::TypescriptNoShadow(_) => TypescriptNoShadow::IS_TSGOLINT_RULE, Self::TypescriptNoThisAlias(_) => TypescriptNoThisAlias::IS_TSGOLINT_RULE, Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { TypescriptNoUnnecessaryBooleanLiteralCompare::IS_TSGOLINT_RULE @@ -15760,7 +15740,6 @@ impl RuleEnum { } Self::TypescriptNoRequireImports(_) => TypescriptNoRequireImports::HAS_CONFIG, Self::TypescriptNoRestrictedTypes(_) => TypescriptNoRestrictedTypes::HAS_CONFIG, - Self::TypescriptNoShadow(_) => TypescriptNoShadow::HAS_CONFIG, Self::TypescriptNoThisAlias(_) => TypescriptNoThisAlias::HAS_CONFIG, Self::TypescriptNoUnnecessaryBooleanLiteralCompare(_) => { TypescriptNoUnnecessaryBooleanLiteralCompare::HAS_CONFIG @@ -16554,7 +16533,6 @@ impl RuleEnum { Self::TypescriptNoRedundantTypeConstituents(rule) => rule.types_info(), Self::TypescriptNoRequireImports(rule) => rule.types_info(), Self::TypescriptNoRestrictedTypes(rule) => rule.types_info(), - Self::TypescriptNoShadow(rule) => rule.types_info(), Self::TypescriptNoThisAlias(rule) => rule.types_info(), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(rule) => rule.types_info(), Self::TypescriptNoUnnecessaryCondition(rule) => rule.types_info(), @@ -17236,7 +17214,6 @@ impl RuleEnum { Self::TypescriptNoRedundantTypeConstituents(rule) => rule.run_info(), Self::TypescriptNoRequireImports(rule) => rule.run_info(), Self::TypescriptNoRestrictedTypes(rule) => rule.run_info(), - Self::TypescriptNoShadow(rule) => rule.run_info(), Self::TypescriptNoThisAlias(rule) => rule.run_info(), Self::TypescriptNoUnnecessaryBooleanLiteralCompare(rule) => rule.run_info(), Self::TypescriptNoUnnecessaryCondition(rule) => rule.run_info(), @@ -17968,7 +17945,6 @@ pub static RULES: std::sync::LazyLock> = std::sync::LazyLock::new( ), RuleEnum::TypescriptNoRequireImports(TypescriptNoRequireImports::default()), RuleEnum::TypescriptNoRestrictedTypes(TypescriptNoRestrictedTypes::default()), - RuleEnum::TypescriptNoShadow(TypescriptNoShadow::default()), RuleEnum::TypescriptNoThisAlias(TypescriptNoThisAlias::default()), RuleEnum::TypescriptNoUnnecessaryBooleanLiteralCompare( TypescriptNoUnnecessaryBooleanLiteralCompare::default(), diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 140ca978343c7..6a202976a6087 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -257,7 +257,6 @@ pub(crate) mod typescript { pub mod no_redundant_type_constituents; pub mod no_require_imports; pub mod no_restricted_types; - pub mod no_shadow; pub mod no_this_alias; pub mod no_unnecessary_boolean_literal_compare; pub mod no_unnecessary_condition; diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow.rs b/crates/oxc_linter/src/rules/eslint/no_shadow.rs deleted file mode 100644 index a3287154f5408..0000000000000 --- a/crates/oxc_linter/src/rules/eslint/no_shadow.rs +++ /dev/null @@ -1,272 +0,0 @@ -use oxc_diagnostics::OxcDiagnostic; -use oxc_macros::declare_oxc_lint; -use oxc_span::{CompactStr, Span}; -use oxc_syntax::symbol::SymbolFlags; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::{ - context::LintContext, - rule::{DefaultRuleConfig, Rule}, -}; - -fn no_shadow_diagnostic(span: Span, name: &str, shadowed_span: Span) -> OxcDiagnostic { - 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"), - ]) -} - -/// Controls how hoisting is handled when checking for shadowing. -#[derive(Debug, Clone, Default, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum HoistOption { - /// Report shadowing even before the outer variable is declared (due to hoisting). - All, - /// Only report shadowing for function declarations that are hoisted. - #[default] - Functions, - /// Never report shadowing before the outer variable is declared. - Never, -} - -#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] -#[serde(rename_all = "camelCase", default, deny_unknown_fields)] -pub struct NoShadowConfig { - /// Controls how hoisting is handled. - #[serde(default)] - pub hoist: HoistOption, - - /// List of variable names that are allowed to shadow. - #[serde(default)] - pub allow: Vec, -} - -impl Default for NoShadowConfig { - fn default() -> Self { - Self { hoist: HoistOption::default(), allow: Vec::new() } - } -} - -#[derive(Debug, Default, Clone)] -pub struct NoShadow(Box); - -impl std::ops::Deref for NoShadow { - type Target = NoShadowConfig; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -declare_oxc_lint!( - /// ### What it does - /// - /// Disallows variable declarations from shadowing variables declared in the outer scope. - /// - /// ### Why is this bad? - /// - /// Shadowing is the process by which a local variable shares the same name as a variable - /// in its containing scope. This can cause confusion, as it may be unclear which variable - /// is being referenced, and can lead to bugs that are difficult to diagnose. - /// - /// ### Examples - /// - /// Examples of **incorrect** code for this rule: - /// ```js - /// var x = 1; - /// function foo() { - /// var x = 2; // x shadows the outer x - /// } - /// ``` - /// - /// Examples of **correct** code for this rule: - /// ```js - /// var x = 1; - /// function foo() { - /// var y = 2; // different name, no shadowing - /// } - /// ``` - NoShadow, - eslint, - suspicious, - config = NoShadowConfig -); - -impl Rule for NoShadow { - fn from_configuration(value: serde_json::Value) -> Result { - serde_json::from_value::>(value) - .map(|c| Self(Box::new(c.into_inner()))) - } - - fn run_once(&self, ctx: &LintContext) { - let scoping = ctx.scoping(); - - for symbol_id in scoping.symbol_ids() { - let symbol_name = scoping.symbol_name(symbol_id); - - // Skip if in allow list - if self.allow.iter().any(|allowed| allowed.as_str() == symbol_name) { - continue; - } - - let symbol_scope = scoping.symbol_scope_id(symbol_id); - let symbol_flags = scoping.symbol_flags(symbol_id); - let symbol_span = scoping.symbol_span(symbol_id); - - // Skip enum members - they don't shadow outer variables - if symbol_flags.contains(SymbolFlags::EnumMember) { - continue; - } - - // Walk parent scopes looking for shadowed variables - for parent_scope in scoping.scope_ancestors(symbol_scope).skip(1) { - if let Some(shadowed_symbol_id) = scoping.get_binding(parent_scope, symbol_name) { - let shadowed_flags = scoping.symbol_flags(shadowed_symbol_id); - let shadowed_span = scoping.symbol_span(shadowed_symbol_id); - - // Check hoisting rules - if !self.check_hoisting(symbol_span, shadowed_span, shadowed_flags) { - continue; - } - - // Report the shadowing - ctx.diagnostic(no_shadow_diagnostic(symbol_span, symbol_name, shadowed_span)); - break; - } - } - } - } -} - -impl NoShadow { - /// Check if shadowing should be reported based on hoisting rules. - pub fn check_hoisting( - &self, - symbol_span: Span, - shadowed_span: Span, - shadowed_flags: SymbolFlags, - ) -> bool { - match self.hoist { - HoistOption::All => true, - HoistOption::Functions => { - // Only report if the shadowed variable is a function or if the symbol - // comes after the shadowed declaration - shadowed_flags.contains(SymbolFlags::Function) - || symbol_span.start >= shadowed_span.start - } - HoistOption::Never => { - // Only report if the symbol comes after the shadowed declaration - symbol_span.start >= shadowed_span.start - } - } - } -} - -#[test] -fn test() { - use crate::tester::Tester; - - let pass = vec![ - // Different names - no shadowing - ("var x = 1; function foo() { var y = 2; }", None), - // Same name in different functions - no shadowing - ("function foo(x) { } function bar(x) { }", None), - // Allowed names - ("var x = 1; function foo() { var x = 2; }", Some(serde_json::json!([{ "allow": ["x"] }]))), - // Reassign - ("let x = true; if (x) { x = false; }", Some(serde_json::json!([{ "allow": ["x"] }]))), - // --- hoist = never: do NOT report if the outer declaration happens later --- - ( - "function f() { { let x = 1; } let x = 2; }", - Some(serde_json::json!([{ "hoist": "never" }])), - ), - // hoist = never: even if the outer is a function declaration, "never" should NOT report when it appears later - ( - "function f() { { let x = 1; } function x() {} }", - Some(serde_json::json!([{ "hoist": "never" }])), - ), - // --- hoist = functions: do NOT report if the outer declaration happens later and it is NOT a function declaration --- - ( - "function f() { { let x = 1; } var x = 2; }", - Some(serde_json::json!([{ "hoist": "functions" }])), - ), - ( - "function f() { { let C = 1; } class C {} }", - Some(serde_json::json!([{ "hoist": "functions" }])), - ), - // --- allow: should suppress the diagnostic regardless of hoist setting --- - ( - "function f() { { let x = 1; } let x = 2; }", - Some(serde_json::json!([{ "hoist": "all", "allow": ["x"] }])), - ), - // allow multiple names - ( - "let x = 1; function f(){ let x = 2; } let y = 1; function g(){ let y = 2; }", - Some(serde_json::json!([{ "allow": ["x", "y"] }])), - ), - // allow applied to destructuring (you already have the failing version; this ensures the escape hatch works) - ("const x = 1; { const { x } = { x: 2 }; }", Some(serde_json::json!([{ "allow": ["x"] }]))), - // Outer is NOT a function declaration; it's a const variable initialized with a function expression. - ( - "function f() { { let x = 1; } const x = function() {}; }", - Some(serde_json::json!([{ "hoist": "functions" }])), - ), - ]; - - let fail = vec![ - // Basic shadowing - ("var x = 1; function foo() { var x = 2; }", None), - // Block scope shadowing - ("const x = 1; { const x = 2; }", None), - // Parameter shadowing outer variable - ("var x = 1; function foo(x) { }", None), - // Nested function shadowing - ("function foo() { var x = 1; function bar() { var x = 2; } }", None), - // Arrow function shadowing - ("const x = 1; const foo = () => { const x = 2; };", None), - // Class method shadowing - ("const x = 1; class Foo { method() { const x = 2; } }", None), - // Let shadowing - ("let x = 1; { let x = 2; }", None), - // Catch clause shadowing - ("const e = 1; try { } catch (e) { }", None), - // For loop variable shadowing - ("const i = 1; for (let i = 0; i < 10; i++) { }", None), - // Destructuring shadowing in nested scope - ("const x = 1; { const { x } = { x: 2 }; }", None), - // Array destructuring shadowing in nested scope - ("const x = 1; { const [x] = [2]; }", None), - ("let x = 1; { { let x = 3; } let x = 2; }", None), - // --- hoist = all: DO report even if the outer declaration happens later --- - ( - "function f() { { let x = 1; } let x = 2; }", - Some(serde_json::json!([{ "hoist": "all" }])), - ), - // --- hoist = functions: DO report if the shadowed symbol is a function declaration even when it appears later --- - ( - "function f() { { let x = 1; } function x() {} }", - Some(serde_json::json!([{ "hoist": "functions" }])), - ), - // hoist = all: should also report when the outer is var and appears later (generalized hoisting behavior) - ( - "function f() { { let x = 1; } var x = 2; }", - Some(serde_json::json!([{ "hoist": "all" }])), - ), - // hoist = never: should still report the normal case (outer first, inner later) - ( - "function f() { let x = 2; { let x = 1; } }", - Some(serde_json::json!([{ "hoist": "never" }])), - ), - // --- allow: allowing only "x" must NOT allow "y" --- - ("let y = 1; function g(){ let y = 2; }", Some(serde_json::json!([{ "allow": ["x"] }]))), - // allow is case-sensitive - ("let x = 1; function f(){ let x = 2; }", Some(serde_json::json!([{ "allow": ["X"] }]))), - ]; - - Tester::new(NoShadow::NAME, NoShadow::PLUGIN, pass, fail).test_and_snapshot(); -} diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/diagnostic.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/diagnostic.rs new file mode 100644 index 0000000000000..7bab079e194f6 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/diagnostic.rs @@ -0,0 +1,13 @@ +use oxc_diagnostics::OxcDiagnostic; +use oxc_span::Span; + +pub fn no_shadow(span: Span, name: &str, shadowed_span: Span) -> OxcDiagnostic { + 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"), + ]) +} diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs new file mode 100644 index 0000000000000..67fe7e4a7c64a --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs @@ -0,0 +1,234 @@ +mod diagnostic; +mod options; + +#[cfg(test)] +mod tests; + +use oxc_ast::AstKind; + +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; +use oxc_syntax::symbol::SymbolFlags; + +use crate::{ + context::LintContext, + rule::{DefaultRuleConfig, Rule}, +}; + +pub use options::{HoistOption, NoShadowConfig}; + + + +#[derive(Debug, Default, Clone)] +pub struct NoShadow(Box); + +impl std::ops::Deref for NoShadow { + type Target = NoShadowConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallows variable declarations from shadowing variables declared in the outer scope. + /// + /// ### Why is this bad? + /// + /// Shadowing is the process by which a local variable shares the same name as a variable + /// in its containing scope. This can cause confusion, as it may be unclear which variable + /// is being referenced, and can lead to bugs that are difficult to diagnose. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// var x = 1; + /// function foo() { + /// var x = 2; // x shadows the outer x + /// } + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// var x = 1; + /// function foo() { + /// var y = 2; // different name, no shadowing + /// } + /// ``` + /// + /// ### TypeScript + /// + /// This rule supports TypeScript-specific options: + /// - `ignoreTypeValueShadow`: When `true` (default), ignores cases where a type and a value + /// have the same name (e.g., `type Foo = string; const Foo = 'bar';`). + /// - `ignoreFunctionTypeParameterNameValueShadow`: When `true` (default), ignores cases where + /// a function type parameter shadows a value (e.g., `const T = 1; function foo() {}`). + NoShadow, + eslint, + suspicious, + config = NoShadowConfig +); + +impl Rule for NoShadow { + fn from_configuration(value: serde_json::Value) -> Result { + serde_json::from_value::>(value) + .map(|c| Self(Box::new(c.into_inner()))) + } + + fn run_once(&self, ctx: &LintContext) { + let scoping = ctx.scoping(); + + for symbol_id in scoping.symbol_ids() { + let symbol_name = scoping.symbol_name(symbol_id); + + // Skip if in allow list + if self.allow.iter().any(|allowed| allowed.as_str() == symbol_name) { + continue; + } + + let symbol_scope = scoping.symbol_scope_id(symbol_id); + let symbol_flags = scoping.symbol_flags(symbol_id); + let symbol_span = scoping.symbol_span(symbol_id); + + // Skip enum members - they don't shadow outer variables + if symbol_flags.contains(SymbolFlags::EnumMember) { + continue; + } + + // Walk parent scopes looking for shadowed variables + for parent_scope in scoping.scope_ancestors(symbol_scope).skip(1) { + if let Some(shadowed_symbol_id) = scoping.get_binding(parent_scope, symbol_name) { + let shadowed_flags = scoping.symbol_flags(shadowed_symbol_id); + let shadowed_span = scoping.symbol_span(shadowed_symbol_id); + + // Check if we should ignore this shadowing based on TypeScript rules + if self.should_ignore_shadow( + ctx, + symbol_id, + symbol_flags, + shadowed_symbol_id, + shadowed_flags, + ) { + break; + } + + // Check hoisting rules + if !self.check_hoisting(symbol_span, shadowed_span, shadowed_flags) { + continue; + } + + // Report the shadowing + // Report the shadowing + ctx.diagnostic(diagnostic::no_shadow(symbol_span, symbol_name, shadowed_span)); + break; + } + } + } + } +} + +impl NoShadow { + /// Check if we should ignore this shadowing based on TypeScript-specific rules. + fn should_ignore_shadow( + &self, + ctx: &LintContext, + symbol_id: oxc_syntax::symbol::SymbolId, + symbol_flags: SymbolFlags, + shadowed_symbol_id: oxc_syntax::symbol::SymbolId, + shadowed_flags: SymbolFlags, + ) -> bool { + // Check type vs value shadowing + if self.ignore_type_value_shadow { + let symbol_is_type = is_type_only(symbol_flags); + let shadowed_is_type = is_type_only(shadowed_flags); + + // If one is a type and the other is a value, ignore + if symbol_is_type != shadowed_is_type { + return true; + } + } + + // Check function type parameter shadowing value + if self.ignore_function_type_parameter_name_value_shadow + && symbol_flags.contains(SymbolFlags::TypeParameter) + { + // Check if the type parameter is in a function context + let declaration_node_id = ctx.scoping().symbol_declaration(symbol_id); + let declaration_node = ctx.nodes().get_node(declaration_node_id); + + // Walk up to find if we're in a function + for ancestor in ctx.nodes().ancestor_ids(declaration_node.id()) { + let ancestor_node = ctx.nodes().get_node(ancestor); + if matches!( + ancestor_node.kind(), + AstKind::Function(_) + | AstKind::ArrowFunctionExpression(_) + | AstKind::TSMethodSignature(_) + | AstKind::TSCallSignatureDeclaration(_) + | AstKind::TSConstructSignatureDeclaration(_) + ) { + // This is a function type parameter, check if shadowed is a value + if !is_type_only(shadowed_flags) { + return true; + } + break; + } + } + } + + // Check if shadowing an import that's only used as a type + if shadowed_flags.contains(SymbolFlags::Import) { + // Check if all references to the import are type-only + let references: Vec<_> = + ctx.scoping().get_resolved_references(shadowed_symbol_id).collect(); + let has_refs = !references.is_empty(); + let all_type_refs = references.iter().all(|r| r.is_type()); + + if has_refs && all_type_refs && !is_type_only(symbol_flags) { + // The import is only used as a type, and we're declaring a value + // This is allowed in TypeScript + return true; + } + } + + false + } + + /// Check if shadowing should be reported based on hoisting rules. + pub fn check_hoisting( + &self, + symbol_span: Span, + shadowed_span: Span, + shadowed_flags: SymbolFlags, + ) -> bool { + match self.hoist { + HoistOption::All => true, + HoistOption::Functions => { + // Only report if the shadowed variable is a function or if the symbol + // comes after the shadowed declaration + shadowed_flags.contains(SymbolFlags::Function) + || symbol_span.start >= shadowed_span.start + } + HoistOption::Never => { + // Only report if the symbol comes after the shadowed declaration + symbol_span.start >= shadowed_span.start + } + } + } +} + +/// Check if the symbol is a type-only declaration (not a value). +fn is_type_only(flags: SymbolFlags) -> bool { + flags.intersects(SymbolFlags::TypeAlias | SymbolFlags::Interface | SymbolFlags::TypeParameter) + && !flags.intersects( + SymbolFlags::FunctionScopedVariable + | SymbolFlags::BlockScopedVariable + | SymbolFlags::Function + | SymbolFlags::Class + | SymbolFlags::Enum + | SymbolFlags::ConstEnum, + ) +} diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/options.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/options.rs new file mode 100644 index 0000000000000..a1966314bbb19 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/options.rs @@ -0,0 +1,53 @@ +use oxc_span::CompactStr; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Controls how hoisting is handled when checking for shadowing. +#[derive(Debug, Clone, Default, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum HoistOption { + /// Report shadowing even before the outer variable is declared (due to hoisting). + All, + /// Only report shadowing for function declarations that are hoisted. + #[default] + Functions, + /// Never report shadowing before the outer variable is declared. + Never, +} + +#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", default, deny_unknown_fields)] +pub struct NoShadowConfig { + /// Controls how hoisting is handled. + #[serde(default)] + pub hoist: HoistOption, + + /// List of variable names that are allowed to shadow. + #[serde(default)] + pub allow: Vec, + + /// If `true`, ignore when a type and a value have the same name. + /// This is common in TypeScript: `type Foo = ...; const Foo = ...;` + #[serde(default = "default_true")] + pub ignore_type_value_shadow: bool, + + /// If `true`, ignore when a function type parameter shadows a value. + /// Example: `const T = 1; function foo() {}` + #[serde(default = "default_true")] + pub ignore_function_type_parameter_name_value_shadow: bool, +} + +fn default_true() -> bool { + true +} + +impl Default for NoShadowConfig { + fn default() -> Self { + Self { + hoist: HoistOption::default(), + allow: Vec::new(), + ignore_type_value_shadow: true, + ignore_function_type_parameter_name_value_shadow: true, + } + } +} diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs new file mode 100644 index 0000000000000..74160d8b8bca8 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs @@ -0,0 +1,174 @@ +use super::NoShadow; +use crate::rule::RuleMeta; +use crate::tester::Tester; + +#[test] +fn test() { + let pass = vec![ + // Different names - no shadowing + ("var x = 1; function foo() { var y = 2; }", None), + // Same name in different functions - no shadowing + ("function foo(x) { } function bar(x) { }", None), + // Type vs value with same name (TypeScript) - default ignored + ("type Foo = string; const Foo = 'bar';", None), + // Interface vs value with same name + ("interface Foo { x: number } const Foo = { x: 1 };", None), + // Type parameter shadowing value (default ignored) + ("const T = 1; function foo() { }", None), + // Enum member doesn't shadow + ("const Red = 1; enum Color { Red, Green, Blue }", None), + // Class with same name as type (declaration merging) + ("interface Foo { x: number } class Foo { x = 1; }", None), + // Namespace with same name as class + ("class Foo { } namespace Foo { export const x = 1; }", None), + // Import used only as type, value with same name + ("import { Foo } from './foo'; type X = Foo; const Foo = 1;", None), + // Allowed names + ("var x = 1; function foo() { var x = 2; }", Some(serde_json::json!([{ "allow": ["x"] }]))), + // Reassign + ("let x = true; if (x) { x = false; }", Some(serde_json::json!([{ "allow": ["x"] }]))), + // --- hoist = never: do NOT report if the outer declaration happens later --- + ( + "function f() { { let x = 1; } let x = 2; }", + Some(serde_json::json!([{ "hoist": "never" }])), + ), + // hoist = never: even if the outer is a function declaration, "never" should NOT report when it appears later + ( + "function f() { { let x = 1; } function x() {} }", + Some(serde_json::json!([{ "hoist": "never" }])), + ), + // --- hoist = functions: do NOT report if the outer declaration happens later and it is NOT a function declaration --- + ( + "function f() { { let x = 1; } var x = 2; }", + Some(serde_json::json!([{ "hoist": "functions" }])), + ), + ( + "function f() { { let C = 1; } class C {} }", + Some(serde_json::json!([{ "hoist": "functions" }])), + ), + // --- allow: should suppress the diagnostic regardless of hoist setting --- + ( + "function f() { { let x = 1; } let x = 2; }", + Some(serde_json::json!([{ "hoist": "all", "allow": ["x"] }])), + ), + // allow multiple names + ( + "let x = 1; function f(){ let x = 2; } let y = 1; function g(){ let y = 2; }", + Some(serde_json::json!([{ "allow": ["x", "y"] }])), + ), + // allow applied to destructuring (you already have the failing version; this ensures the escape hatch works) + ("const x = 1; { const { x } = { x: 2 }; }", Some(serde_json::json!([{ "allow": ["x"] }]))), + // Outer is NOT a function declaration; it's a const variable initialized with a function expression. + ( + "function f() { { let x = 1; } const x = function() {}; }", + Some(serde_json::json!([{ "hoist": "functions" }])), + ), + // ------------------------- + // TypeScript specific pass cases + // ------------------------- + // `allow` should also override type/value behavior even when ignoreTypeValueShadow=false. + ( + "type Foo = string; { const Foo = 'bar'; }", + Some(serde_json::json!([{ + "ignoreTypeValueShadow": false, + "allow": ["Foo"] + }])), + ), + // If ignoreTypeValueShadow=false, this would normally be reportable... + // ...but with ignoreFunctionTypeParameterNameValueShadow=true it must be ignored (pass). + ( + "const T = 1; function foo() { }", + Some(serde_json::json!([{ + "ignoreTypeValueShadow": false, + "ignoreFunctionTypeParameterNameValueShadow": true + }])), + ), + ]; + + let fail = vec![ + // Basic shadowing + ("var x = 1; function foo() { var x = 2; }", None), + // Block scope shadowing + ("const x = 1; { const x = 2; }", None), + // Parameter shadowing outer variable + ("var x = 1; function foo(x) { }", None), + // Nested function shadowing + ("function foo() { var x = 1; function bar() { var x = 2; } }", None), + // Arrow function shadowing + ("const x = 1; const foo = () => { const x = 2; };", None), + // Class method shadowing + ("const x = 1; class Foo { method() { const x = 2; } }", None), + // Let shadowing + ("let x = 1; { let x = 2; }", None), + // Catch clause shadowing + ("const e = 1; try { } catch (e) { }", None), + // For loop variable shadowing + ("const i = 1; for (let i = 0; i < 10; i++) { }", None), + // Destructuring shadowing in nested scope + ("const x = 1; { const { x } = { x: 2 }; }", None), + // Array destructuring shadowing in nested scope + ("const x = 1; { const [x] = [2]; }", None), + ("let x = 1; { { let x = 3; } let x = 2; }", None), + // Type shadowing type (not ignored) + ("type Foo = string; { type Foo = number; }", None), + // Interface shadowing interface + ("interface Foo { x: number } { interface Foo { y: string } }", None), + // --- hoist = all: DO report even if the outer declaration happens later --- + ( + "function f() { { let x = 1; } let x = 2; }", + Some(serde_json::json!([{ "hoist": "all" }])), + ), + // --- hoist = functions: DO report if the shadowed symbol is a function declaration even when it appears later --- + ( + "function f() { { let x = 1; } function x() {} }", + Some(serde_json::json!([{ "hoist": "functions" }])), + ), + // hoist = all: should also report when the outer is var and appears later (generalized hoisting behavior) + ( + "function f() { { let x = 1; } var x = 2; }", + Some(serde_json::json!([{ "hoist": "all" }])), + ), + // hoist = never: should still report the normal case (outer first, inner later) + ( + "function f() { let x = 2; { let x = 1; } }", + Some(serde_json::json!([{ "hoist": "never" }])), + ), + // --- allow: allowing only "x" must NOT allow "y" --- + ("let y = 1; function g(){ let y = 2; }", Some(serde_json::json!([{ "allow": ["x"] }]))), + // allow is case-sensitive + ("let x = 1; function f(){ let x = 2; }", Some(serde_json::json!([{ "allow": ["X"] }]))), + // ------------------------- + // TypeScript specific fail cases + // ------------------------- + // ignoreTypeValueShadow = false => type/value with the same name is now reportable + ( + "type Foo = string; { const Foo = 'bar'; }", + Some(serde_json::json!([{ "ignoreTypeValueShadow": false }])), + ), + ( + "interface Foo { x: number }; { const Foo = { x: 1 } };", + Some(serde_json::json!([{ "ignoreTypeValueShadow": false }])), + ), + // ignoreFunctionTypeParameterNameValueShadow = false + // (and ignoreTypeValueShadow=false to ensure this option is the one deciding) + // Now the function type parameter shadowing a value should be reported. + ( + "const T = 1; function foo() { }", + Some(serde_json::json!([{ + "ignoreTypeValueShadow": false, + "ignoreFunctionTypeParameterNameValueShadow": false + }])), + ), + // Enum shadowing Enum (Enums are values) + ("enum E { A } function foo() { enum E { B } }", None), + // Class shadowing Class (Classes are values) + ("class C { } function foo() { class C { } }", None), + // Import (Value) shadowing Value + // The import is used as a value, so it should be shadowed + ("import { Foo } from './foo'; const x = Foo; function bar() { const Foo = 1; }", None), + // Generic Parameter shadowing Generic Parameter + ("function foo() { function bar() { } }", None), + ]; + + Tester::new(NoShadow::NAME, NoShadow::PLUGIN, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/rules/typescript/no_shadow.rs b/crates/oxc_linter/src/rules/typescript/no_shadow.rs deleted file mode 100644 index e141335196353..0000000000000 --- a/crates/oxc_linter/src/rules/typescript/no_shadow.rs +++ /dev/null @@ -1,456 +0,0 @@ -use oxc_ast::AstKind; -use oxc_diagnostics::OxcDiagnostic; -use oxc_macros::declare_oxc_lint; -use oxc_span::{CompactStr, Span}; -use oxc_syntax::symbol::SymbolFlags; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::{ - context::LintContext, - rule::{DefaultRuleConfig, Rule}, -}; - -fn no_shadow_diagnostic(span: Span, name: &str, shadowed_span: Span) -> OxcDiagnostic { - 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"), - ]) -} - -/// Controls how hoisting is handled when checking for shadowing. -#[derive(Debug, Clone, Default, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum HoistOption { - /// Report shadowing even before the outer variable is declared (due to hoisting). - All, - /// Only report shadowing for function declarations that are hoisted. - #[default] - Functions, - /// Never report shadowing before the outer variable is declared. - Never, -} - -#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] -#[serde(rename_all = "camelCase", default, deny_unknown_fields)] -pub struct NoShadowConfig { - /// Controls how hoisting is handled. - #[serde(default)] - pub hoist: HoistOption, - - /// List of variable names that are allowed to shadow. - #[serde(default)] - pub allow: Vec, - - /// If `true`, ignore when a type and a value have the same name. - /// This is common in TypeScript: `type Foo = ...; const Foo = ...;` - #[serde(default = "default_true")] - pub ignore_type_value_shadow: bool, - - /// If `true`, ignore when a function type parameter shadows a value. - /// Example: `const T = 1; function foo() {}` - #[serde(default = "default_true")] - pub ignore_function_type_parameter_name_value_shadow: bool, -} - -fn default_true() -> bool { - true -} - -impl Default for NoShadowConfig { - fn default() -> Self { - Self { - hoist: HoistOption::default(), - allow: Vec::new(), - ignore_type_value_shadow: true, - ignore_function_type_parameter_name_value_shadow: true, - } - } -} - -#[derive(Debug, Default, Clone)] -pub struct NoShadow(Box); - -impl std::ops::Deref for NoShadow { - type Target = NoShadowConfig; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -declare_oxc_lint!( - /// ### What it does - /// - /// Disallows variable declarations from shadowing variables declared in the outer scope. - /// - /// ### Why is this bad? - /// - /// Shadowing is the process by which a local variable shares the same name as a variable - /// in its containing scope. This can cause confusion, as it may be unclear which variable - /// is being referenced, and can lead to bugs that are difficult to diagnose. - /// - /// ### Examples - /// - /// Examples of **incorrect** code for this rule: - /// ```ts - /// const x = 1; - /// function foo() { - /// const x = 2; // x shadows the outer x - /// } - /// ``` - /// - /// Examples of **correct** code for this rule: - /// ```ts - /// const x = 1; - /// function foo() { - /// const y = 2; // different name, no shadowing - /// } - /// ``` - NoShadow, - typescript, - suspicious, - config = NoShadowConfig -); - -impl Rule for NoShadow { - fn from_configuration(value: serde_json::Value) -> Result { - serde_json::from_value::>(value) - .map(|c| Self(Box::new(c.into_inner()))) - } - - fn run_once(&self, ctx: &LintContext) { - let scoping = ctx.scoping(); - - for symbol_id in scoping.symbol_ids() { - let symbol_name = scoping.symbol_name(symbol_id); - - // Skip if in allow list - if self.allow.iter().any(|allowed| allowed.as_str() == symbol_name) { - continue; - } - - let symbol_scope = scoping.symbol_scope_id(symbol_id); - let symbol_flags = scoping.symbol_flags(symbol_id); - let symbol_span = scoping.symbol_span(symbol_id); - - // Skip enum members - they don't shadow outer variables - if symbol_flags.contains(SymbolFlags::EnumMember) { - continue; - } - - // Walk parent scopes looking for shadowed variables - for parent_scope in scoping.scope_ancestors(symbol_scope).skip(1) { - if let Some(shadowed_symbol_id) = scoping.get_binding(parent_scope, symbol_name) { - let shadowed_flags = scoping.symbol_flags(shadowed_symbol_id); - let shadowed_span = scoping.symbol_span(shadowed_symbol_id); - - // Check if we should ignore this shadowing based on TypeScript rules - if self.should_ignore_shadow( - ctx, - symbol_id, - symbol_flags, - shadowed_symbol_id, - shadowed_flags, - ) { - break; - } - - // Check hoisting rules - if !self.check_hoisting(symbol_span, shadowed_span, shadowed_flags) { - continue; - } - - // Report the shadowing - ctx.diagnostic(no_shadow_diagnostic(symbol_span, symbol_name, shadowed_span)); - break; - } - } - } - } -} - -impl NoShadow { - /// Check if we should ignore this shadowing based on TypeScript-specific rules. - fn should_ignore_shadow( - &self, - ctx: &LintContext, - symbol_id: oxc_syntax::symbol::SymbolId, - symbol_flags: SymbolFlags, - shadowed_symbol_id: oxc_syntax::symbol::SymbolId, - shadowed_flags: SymbolFlags, - ) -> bool { - // Check type vs value shadowing - if self.ignore_type_value_shadow { - let symbol_is_type = is_type_only(symbol_flags); - let shadowed_is_type = is_type_only(shadowed_flags); - - // If one is a type and the other is a value, ignore - if symbol_is_type != shadowed_is_type { - return true; - } - } - - // Check function type parameter shadowing value - if self.ignore_function_type_parameter_name_value_shadow - && symbol_flags.contains(SymbolFlags::TypeParameter) - { - // Check if the type parameter is in a function context - let declaration_node_id = ctx.scoping().symbol_declaration(symbol_id); - let declaration_node = ctx.nodes().get_node(declaration_node_id); - - // Walk up to find if we're in a function - for ancestor in ctx.nodes().ancestor_ids(declaration_node.id()) { - let ancestor_node = ctx.nodes().get_node(ancestor); - if matches!( - ancestor_node.kind(), - AstKind::Function(_) - | AstKind::ArrowFunctionExpression(_) - | AstKind::TSMethodSignature(_) - | AstKind::TSCallSignatureDeclaration(_) - | AstKind::TSConstructSignatureDeclaration(_) - ) { - // This is a function type parameter, check if shadowed is a value - if !is_type_only(shadowed_flags) { - return true; - } - break; - } - } - } - - // Check if shadowing an import that's only used as a type - if shadowed_flags.contains(SymbolFlags::Import) { - // Check if all references to the import are type-only - let references: Vec<_> = - ctx.scoping().get_resolved_references(shadowed_symbol_id).collect(); - let has_refs = !references.is_empty(); - let all_type_refs = references.iter().all(|r| r.is_type()); - - if has_refs && all_type_refs && !is_type_only(symbol_flags) { - // The import is only used as a type, and we're declaring a value - // This is allowed in TypeScript - return true; - } - } - - false - } - - /// Check if shadowing should be reported based on hoisting rules. - fn check_hoisting( - &self, - symbol_span: Span, - shadowed_span: Span, - shadowed_flags: SymbolFlags, - ) -> bool { - match self.hoist { - HoistOption::All => true, - HoistOption::Functions => { - // Only report if the shadowed variable is a function or if the symbol - // comes after the shadowed declaration - shadowed_flags.contains(SymbolFlags::Function) - || symbol_span.start >= shadowed_span.start - } - HoistOption::Never => { - // Only report if the symbol comes after the shadowed declaration - symbol_span.start >= shadowed_span.start - } - } - } -} - -/// Check if the symbol is a type-only declaration (not a value). -fn is_type_only(flags: SymbolFlags) -> bool { - flags.intersects(SymbolFlags::TypeAlias | SymbolFlags::Interface | SymbolFlags::TypeParameter) - && !flags.intersects( - SymbolFlags::FunctionScopedVariable - | SymbolFlags::BlockScopedVariable - | SymbolFlags::Function - | SymbolFlags::Class - | SymbolFlags::Enum - | SymbolFlags::ConstEnum, - ) -} - -#[test] -fn test() { - use crate::tester::Tester; - - let pass = vec![ - // Different names - no shadowing - ("const x = 1; function foo() { const y = 2; }", None), - // Same name in different functions - no shadowing - ("function foo(x) { } function bar(x) { }", None), - // Type vs value with same name (TypeScript) - default ignored - ("type Foo = string; const Foo = 'bar';", None), - // Interface vs value with same name - ("interface Foo { x: number } const Foo = { x: 1 };", None), - // Allowed names - ( - "const x = 1; function foo() { const x = 2; }", - Some(serde_json::json!([{ "allow": ["x"] }])), - ), - // Type parameter shadowing value (default ignored) - ("const T = 1; function foo() { }", None), - // Enum member doesn't shadow - ("const Red = 1; enum Color { Red, Green, Blue }", None), - // Class with same name as type (declaration merging) - ("interface Foo { x: number } class Foo { x = 1; }", None), - // Namespace with same name as class - ("class Foo { } namespace Foo { export const x = 1; }", None), - // Import used only as type, value with same name - ( - r#" - import { Foo } from './foo'; - type X = Foo; - const Foo = 1; - "#, - None, - ), - // ------------------------- - // allow - // ------------------------- - // `allow` should win even when it would normally be a failure. - ( - "const x = 1; function foo() { const x = 2; }", - Some(serde_json::json!([{ "allow": ["x"] }])), - ), - // `allow` should also override type/value behavior even when ignoreTypeValueShadow=false. - ( - "type Foo = string; const Foo = 'bar';", - Some(serde_json::json!([{ - "ignoreTypeValueShadow": false, - "allow": ["Foo"] - }])), - ), - // ------------------------- - // ignoreTypeValueShadow - // ------------------------- - // Default (true): type vs value with the same name should be ignored. - ("type Foo = string; const Foo = 'bar';", None), - // Same idea for interface vs value (declaration merging is common in TS). - ("interface Foo { x: number } const Foo = { x: 1 };", None), - // ------------------------- - // ignoreFunctionTypeParameterNameValueShadow - // (interaction with ignoreTypeValueShadow) - // ------------------------- - // If ignoreTypeValueShadow=false, this would normally be reportable... - // ...but with ignoreFunctionTypeParameterNameValueShadow=true it must be ignored (pass). - ( - "const T = 1; function foo() { }", - Some(serde_json::json!([{ - "ignoreTypeValueShadow": false, - "ignoreFunctionTypeParameterNameValueShadow": true - }])), - ), - // ------------------------- - // hoist - // ------------------------- - // Outer symbol is `var` (hoisted like var), but declared *after* the inner declaration. - // With hoist=functions or hoist=never, do NOT report shadowing before the outer declaration. - ( - "function outer() { function inner() { const x = 1; } var x = 2; }", - Some(serde_json::json!([{ "hoist": "functions" }])), // default, explicit - ), - ( - "function outer() { function inner() { const x = 1; } var x = 2; }", - Some(serde_json::json!([{ "hoist": "never" }])), - ), - // hoist=never: even if the outer symbol is a function declaration declared later, - // do NOT report it (because we are not considering hoisting). - ( - "function outer() { function inner() { const foo = 1; } function foo() {} }", - Some(serde_json::json!([{ "hoist": "never" }])), - ), - // allow + hoist: even if hoist=all would normally fail, `allow` should still allow it. - ( - "function outer() { function inner() { const x = 1; } var x = 2; }", - Some(serde_json::json!([{ "hoist": "all", "allow": ["x"] }])), - ), - ]; - - let fail = vec![ - // Basic shadowing - ("const x = 1; function foo() { const x = 2; }", None), - // Block scope shadowing - ("const x = 1; { const x = 2; }", None), - // Parameter shadowing outer variable - ("const x = 1; function foo(x) { }", None), - // Nested function shadowing - ("function foo() { const x = 1; function bar() { const x = 2; } }", None), - // Arrow function shadowing - ("const x = 1; const foo = () => { const x = 2; };", None), - // Class method shadowing - ("const x = 1; class Foo { method() { const x = 2; } }", None), - // Let shadowing - ("let x = 1; { let x = 2; }", None), - // Var shadowing (hoisted) - ("var x = 1; function foo() { var x = 2; }", None), - // Type shadowing type (not ignored) - ("type Foo = string; { type Foo = number; }", None), - // Interface shadowing interface - ("interface Foo { x: number } { interface Foo { y: string } }", None), - // Catch clause shadowing - ("const e = 1; try { } catch (e) { }", None), - // For loop variable shadowing - ("const i = 1; for (let i = 0; i < 10; i++) { }", None), - // Destructuring shadowing in nested scope - ("const x = 1; { const { x } = { x: 2 }; }", None), - // Array destructuring shadowing in nested scope - ("const x = 1; { const [x] = [2]; }", None), - // ------------------------- - // ignoreTypeValueShadow = false => type/value with the same name is now reportable - // ------------------------- - ( - "type Foo = string; { const Foo = 'bar'; }", - Some(serde_json::json!([{ "ignoreTypeValueShadow": false }])), - ), - ( - "interface Foo { x: number }; { const Foo = { x: 1 } };", - Some(serde_json::json!([{ "ignoreTypeValueShadow": false }])), - ), - // ------------------------- - // ignoreFunctionTypeParameterNameValueShadow = false - // (and ignoreTypeValueShadow=false to ensure this option is the one deciding) - // ------------------------- - // Now the function type parameter shadowing a value should be reported. - ( - "const T = 1; function foo() { }", - Some(serde_json::json!([{ - "ignoreTypeValueShadow": false, - "ignoreFunctionTypeParameterNameValueShadow": false - }])), - ), - // ------------------------- - // hoist - // ------------------------- - // Outer `var` declared after: with hoist=all it SHOULD be reported - // (because we consider hoisting for all declarations). - ( - "function outer() { function inner() { const x = 1; } var x = 2; }", - Some(serde_json::json!([{ "hoist": "all" }])), - ), - // Outer `function` declared after: with hoist=functions it SHOULD be reported. - ( - "function outer() { function inner() { const foo = 1; } function foo() {} }", - Some(serde_json::json!([{ "hoist": "functions" }])), - ), - // And with hoist=all it should also be reported. - ( - "function outer() { function inner() { const foo = 1; } function foo() {} }", - Some(serde_json::json!([{ "hoist": "all" }])), - ), - // Sanity check: a "normal" shadow (outer declared before) should fail regardless of hoist. - ( - "function outer() { var x = 2; function inner() { const x = 1; } }", - Some(serde_json::json!([{ "hoist": "never" }])), - ), - ]; - - Tester::new(NoShadow::NAME, NoShadow::PLUGIN, pass, fail).test_and_snapshot(); -} diff --git a/crates/oxc_linter/src/snapshots/eslint_no_shadow.snap b/crates/oxc_linter/src/snapshots/eslint_no_shadow.snap index 0d6ec68288eb1..5501531160c26 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_shadow.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_shadow.snap @@ -119,6 +119,24 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:6] + 1 │ type Foo = string; { type Foo = number; } + · ─┬─ ─┬─ + · │ ╰── 'Foo' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:11] + 1 │ interface Foo { x: number } { interface Foo { y: string } } + · ─┬─ ─┬─ + · │ ╰── 'Foo' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. ╭─[no_shadow.tsx:1:22] 1 │ function f() { { let x = 1; } let x = 2; } @@ -172,3 +190,71 @@ source: crates/oxc_linter/src/tester.rs · ╰── shadowed declaration is here ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:6] + 1 │ type Foo = string; { const Foo = 'bar'; } + · ─┬─ ─┬─ + · │ ╰── 'Foo' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:11] + 1 │ interface Foo { x: number }; { const Foo = { x: 1 } }; + · ─┬─ ─┬─ + · │ ╰── 'Foo' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'T' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const T = 1; function foo() { } + · ┬ ┬ + · │ ╰── 'T' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'T' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'E' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:6] + 1 │ enum E { A } function foo() { enum E { B } } + · ┬ ┬ + · │ ╰── 'E' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'E' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'C' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ class C { } function foo() { class C { } } + · ┬ ┬ + · │ ╰── 'C' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'C' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:22] + 1 │ + 2 │ import { Foo } from './foo'; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ const x = Foo; + 4 │ function bar() { const Foo = 1; } + · ─┬─ + · ╰── 'Foo' is declared here + 5 │ + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'T' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:14] + 1 │ function foo() { function bar() { } } + · ┬ ┬ + · │ ╰── 'T' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'T' to avoid shadowing the variable from the outer scope. From beef9248df10a6e49bf17fa2dfbba597adf71a72 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Wed, 11 Feb 2026 12:50:16 +0000 Subject: [PATCH 04/15] move diagnostics to the same module --- .../src/rules/eslint/no_shadow/diagnostic.rs | 13 ----------- .../src/rules/eslint/no_shadow/mod.rs | 23 ++++++++++++++----- 2 files changed, 17 insertions(+), 19 deletions(-) delete mode 100644 crates/oxc_linter/src/rules/eslint/no_shadow/diagnostic.rs diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/diagnostic.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/diagnostic.rs deleted file mode 100644 index 7bab079e194f6..0000000000000 --- a/crates/oxc_linter/src/rules/eslint/no_shadow/diagnostic.rs +++ /dev/null @@ -1,13 +0,0 @@ -use oxc_diagnostics::OxcDiagnostic; -use oxc_span::Span; - -pub fn no_shadow(span: Span, name: &str, shadowed_span: Span) -> OxcDiagnostic { - 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"), - ]) -} diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs index 67fe7e4a7c64a..c3938c8111530 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs @@ -1,4 +1,3 @@ -mod diagnostic; mod options; #[cfg(test)] @@ -6,6 +5,7 @@ mod tests; use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; use oxc_syntax::symbol::SymbolFlags; @@ -17,7 +17,16 @@ use crate::{ pub use options::{HoistOption, NoShadowConfig}; - +pub fn no_shadow_diagnostic(span: Span, name: &str, shadowed_span: Span) -> OxcDiagnostic { + 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"), + ]) +} #[derive(Debug, Default, Clone)] pub struct NoShadow(Box); @@ -82,7 +91,7 @@ impl Rule for NoShadow { let scoping = ctx.scoping(); for symbol_id in scoping.symbol_ids() { - let symbol_name = scoping.symbol_name(symbol_id); + let symbol_name = scoping.symbol_ident(symbol_id); // Skip if in allow list if self.allow.iter().any(|allowed| allowed.as_str() == symbol_name) { @@ -120,9 +129,11 @@ impl Rule for NoShadow { continue; } - // Report the shadowing - // Report the shadowing - ctx.diagnostic(diagnostic::no_shadow(symbol_span, symbol_name, shadowed_span)); + ctx.diagnostic(no_shadow_diagnostic( + symbol_span, + symbol_name.as_str(), + shadowed_span, + )); break; } } From c191ea1e055f1e1d712b5e6a7547fe9483c1021c Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Wed, 11 Feb 2026 12:51:05 +0000 Subject: [PATCH 05/15] add missing options --- .../src/rules/eslint/no_shadow/options.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/options.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/options.rs index a1966314bbb19..abfe91f74d5b5 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow/options.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/options.rs @@ -9,10 +9,14 @@ pub enum HoistOption { /// Report shadowing even before the outer variable is declared (due to hoisting). All, /// Only report shadowing for function declarations that are hoisted. - #[default] Functions, + /// Report shadowing for both function and type declarations that are hoisted. + #[default] + FunctionsAndTypes, /// Never report shadowing before the outer variable is declared. Never, + /// Only report shadowing for type declarations that are hoisted. + Types, } #[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] @@ -35,6 +39,12 @@ pub struct NoShadowConfig { /// Example: `const T = 1; function foo() {}` #[serde(default = "default_true")] pub ignore_function_type_parameter_name_value_shadow: bool, + + /// Whether to report shadowing of built-in global variables. + pub builtin_globals: bool, + + /// Whether to ignore the variable initializers when the shadowed variable is presumably still uninitialized. + pub ignore_on_initialization: bool, } fn default_true() -> bool { @@ -48,6 +58,8 @@ impl Default for NoShadowConfig { allow: Vec::new(), ignore_type_value_shadow: true, ignore_function_type_parameter_name_value_shadow: true, + builtin_globals: false, + ignore_on_initialization: false, } } } From 4a9b69338357f4709f0ba0619ebed27925433497 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Wed, 11 Feb 2026 12:51:11 +0000 Subject: [PATCH 06/15] port all tests --- .../src/rules/eslint/no_shadow/tests.rs | 3729 ++++++++++++++++- 1 file changed, 3593 insertions(+), 136 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs index 74160d8b8bca8..12bb4e738d7de 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs @@ -1,174 +1,3631 @@ use super::NoShadow; use crate::rule::RuleMeta; -use crate::tester::Tester; #[test] -fn test() { +fn test_eslint() { + use crate::tester::Tester; + use std::path::PathBuf; + + let pass = vec![ + ( + "var a=3; function b(x) { a++; return x + a; }; setTimeout(function() { b(a); }, 0);", + None, + None, + None, + ), + ( + "(function() { var doSomething = function doSomething() {}; doSomething() }())", + None, + None, + None, + ), + ( + "(function() { var doSomething = foo || function doSomething() {}; doSomething() }())", + None, + None, + None, + ), + ( + "(function() { var doSomething = function doSomething() {} || foo; doSomething() }())", + None, + None, + None, + ), + ( + "(function() { var doSomething = foo && function doSomething() {}; doSomething() }())", + None, + None, + None, + ), + ( + "(function() { var doSomething = foo ?? function doSomething() {}; doSomething() }())", + None, + None, + None, + ), // { "ecmaVersion": 2020 }, + ( + "(function() { var doSomething = foo || (bar || function doSomething() {}); doSomething() }())", + None, + None, + None, + ), + ( + "(function() { var doSomething = foo || (bar && function doSomething() {}); doSomething() }())", + None, + None, + None, + ), + ( + "(function() { var doSomething = foo ? function doSomething() {} : bar; doSomething() }())", + None, + None, + None, + ), + ( + "(function() { var doSomething = foo ? bar: function doSomething() {}; doSomething() }())", + None, + None, + None, + ), + ( + "(function() { var doSomething = foo ? bar: (baz || function doSomething() {}); doSomething() }())", + None, + None, + None, + ), + ( + "(function() { var doSomething = (foo ? bar: function doSomething() {}) || baz; doSomething() }())", + None, + None, + None, + ), + ( + "(function() { var { doSomething = function doSomething() {} } = obj; doSomething() }())", + None, + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "(function() { var { doSomething = function doSomething() {} || foo } = obj; doSomething() }())", + None, + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "(function() { var { doSomething = foo ? function doSomething() {} : bar } = obj; doSomething() }())", + None, + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "(function() { var { doSomething = foo ? bar : function doSomething() {} } = obj; doSomething() }())", + None, + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "(function() { var { doSomething = foo || (bar ? baz : (qux || function doSomething() {})) || quux } = obj; doSomething() }())", + None, + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo(doSomething = function doSomething() {}) { doSomething(); }", + None, + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo(doSomething = function doSomething() {} || foo) { doSomething(); }", + None, + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo(doSomething = foo ? function doSomething() {} : bar) { doSomething(); }", + None, + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo(doSomething = foo ? bar : function doSomething() {}) { doSomething(); }", + None, + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo(doSomething = foo || (bar ? baz : (qux || function doSomething() {})) || quux) { doSomething(); }", + None, + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "var arguments; + function bar() { }", + None, + None, + None, + ), + ( + "var a=3; var b = (x) => { a++; return x + a; }; setTimeout(() => { b(a); }, 0);", + None, + None, + None, + ), // { "ecmaVersion": 6 }, + ("class A {}", None, None, None), // { "ecmaVersion": 6 }, + ("class A { constructor() { var a; } }", None, None, None), // { "ecmaVersion": 6 }, + ("(function() { var A = class A {}; })()", None, None, None), // { "ecmaVersion": 6 }, + ("(function() { var A = foo || class A {}; })()", None, None, None), // { "ecmaVersion": 6 }, + ("(function() { var A = class A {} || foo; })()", None, None, None), // { "ecmaVersion": 6 }, + ("(function() { var A = foo && class A {} || foo; })()", None, None, None), // { "ecmaVersion": 6 }, + ("(function() { var A = foo ?? class A {}; })()", None, None, None), // { "ecmaVersion": 2020 }, + ("(function() { var A = foo || (bar || class A {}); })()", None, None, None), // { "ecmaVersion": 2020 }, + ("(function() { var A = foo || (bar && class A {}); })()", None, None, None), // { "ecmaVersion": 2020 }, + ("(function() { var A = foo ? class A {} : bar; })()", None, None, None), // { "ecmaVersion": 6 }, + ("(function() { var A = foo ? bar : class A {}; })()", None, None, None), // { "ecmaVersion": 6 }, + ("(function() { var A = foo ? bar: (baz || class A {}); })()", None, None, None), // { "ecmaVersion": 6 }, + ("(function() { var A = (foo ? bar: class A {}) || baz; })()", None, None, None), // { "ecmaVersion": 6 }, + ("(function() { var { A = class A {} } = obj; }())", None, None, None), // { "ecmaVersion": 6 }, + ("(function() { var { A = class A {} || foo } = obj; }())", None, None, None), // { "ecmaVersion": 6 }, + ("(function() { var { A = foo ? class A {} : bar } = obj; }())", None, None, None), // { "ecmaVersion": 6 }, + ("(function() { var { A = foo ? bar : class A {} } = obj; }())", None, None, None), // { "ecmaVersion": 6 }, + ( + "(function() { var { A = foo || (bar ? baz : (qux || class A {})) || quux } = obj; }())", + None, + None, + None, + ), // { "ecmaVersion": 6 }, + ("function foo(A = class A {}) { doSomething(); }", None, None, None), // { "ecmaVersion": 6 }, + ("function foo(A = class A {} || foo) { doSomething(); }", None, None, None), // { "ecmaVersion": 6 }, + ("function foo(A = foo ? class A {} : bar) { doSomething(); }", None, None, None), // { "ecmaVersion": 6 }, + ("function foo(A = foo ? bar : class A {}) { doSomething(); }", None, None, None), // { "ecmaVersion": 6 }, + ( + "function foo(A = foo || (bar ? baz : (qux || class A {})) || quux) { doSomething(); }", + None, + None, + None, + ), // { "ecmaVersion": 6 }, + ("{ var a; } var a;", None, None, None), // { "ecmaVersion": 6 }, + ("{ let a; } let a;", Some(serde_json::json!([{ "hoist": "never" }])), None, None), // { "ecmaVersion": 6 }, + ("{ let a; } var a;", Some(serde_json::json!([{ "hoist": "never" }])), None, None), // { "ecmaVersion": 6 }, + ("{ let a; } function a() {}", Some(serde_json::json!([{ "hoist": "never" }])), None, None), // { "ecmaVersion": 6 }, + ( + "{ const a = 0; } const a = 1;", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ("{ const a = 0; } var a;", Some(serde_json::json!([{ "hoist": "never" }])), None, None), // { "ecmaVersion": 6 }, + ( + "{ const a = 0; } function a() {}", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo() { let a; } let a;", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo() { let a; } var a;", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo() { let a; } function a() {}", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo() { var a; } let a;", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo() { var a; } var a;", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo() { var a; } function a() {}", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ("function foo(a) { } let a;", Some(serde_json::json!([{ "hoist": "never" }])), None, None), // { "ecmaVersion": 6 }, + ("function foo(a) { } var a;", Some(serde_json::json!([{ "hoist": "never" }])), None, None), // { "ecmaVersion": 6 }, + ( + "function foo(a) { } function a() {}", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ("{ let a; } let a;", None, None, None), // { "ecmaVersion": 6 }, + ("{ let a; } var a;", None, None, None), // { "ecmaVersion": 6 }, + ("{ const a = 0; } const a = 1;", None, None, None), // { "ecmaVersion": 6 }, + ("{ const a = 0; } var a;", None, None, None), // { "ecmaVersion": 6 }, + ("function foo() { let a; } let a;", None, None, None), // { "ecmaVersion": 6 }, + ("function foo() { let a; } var a;", None, None, None), // { "ecmaVersion": 6 }, + ("function foo() { var a; } let a;", None, None, None), // { "ecmaVersion": 6 }, + ("function foo() { var a; } var a;", None, None, None), // { "ecmaVersion": 6 }, + ("function foo(a) { } let a;", None, None, None), // { "ecmaVersion": 6 }, + ("function foo(a) { } var a;", None, None, None), // { "ecmaVersion": 6 }, + ("function foo() { var Object = 0; }", None, None, None), + ("function foo() { var top = 0; }", None, None, None), // { "globals": globals.browser }, + ("var Object = 0;", Some(serde_json::json!([{ "builtinGlobals": true }])), None, None), + ("var top = 0;", Some(serde_json::json!([{ "builtinGlobals": true }])), None, None), // { "globals": globals.browser }, + ( + "function foo(cb) { (function (cb) { cb(42); })(cb); }", + Some(serde_json::json!([{ "allow": ["cb"] }])), + None, + None, + ), + ("class C { foo; foo() { let foo; } }", None, None, None), // { "ecmaVersion": 2022 }, + ("class C { static { var x; } static { var x; } }", None, None, None), // { "ecmaVersion": 2022 }, + ("class C { static { let x; } static { let x; } }", None, None, None), // { "ecmaVersion": 2022 }, + ("class C { static { var x; { var x; /* redeclaration */ } } }", None, None, None), // { "ecmaVersion": 2022 }, + ("class C { static { { var x; } { var x; /* redeclaration */ } } }", None, None, None), // { "ecmaVersion": 2022 }, + ("class C { static { { let x; } { let x; } } }", None, None, None), // { "ecmaVersion": 2022 }, + ( + "const a = [].find(a => a)", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const a = [].find(function(a) { return a; })", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const [a = [].find(a => true)] = dummy", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const { a = [].find(a => true) } = dummy", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function func(a = [].find(a => true)) {}", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "for (const a in [].find(a => true)) {}", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "for (const a of [].find(a => true)) {}", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const a = [].map(a => true).filter(a => a === 'b')", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const a = [].map(a => true).filter(a => a === 'b').find(a => a === 'c')", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const { a } = (({ a }) => ({ a }))();", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const person = people.find(item => {const person = item.name; return person === 'foo'})", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "var y = bar || foo(y => y);", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "var y = bar && foo(y => y);", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "var z = bar(foo(z => z));", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "var z = boo(bar(foo(z => z)));", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "var match = function (person) { return person.name === 'foo'; }; + const person = [].find(match);", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const a = foo(x || (a => {}))", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const { a = 1 } = foo(a => {})", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const person = {...people.find((person) => person.firstName.startsWith('s'))}", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 2021 }, + ( + "const person = { firstName: people.filter((person) => person.firstName.startsWith('s')).map((person) => person.firstName)[0]}", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 2021 }, + ( + "() => { const y = foo(y => y); }", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const x = (x => x)()", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "var y = bar || (y => y)();", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "var y = bar && (y => y)();", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "var x = (x => x)((y => y)());", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const { a = 1 } = (a => {})()", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "() => { const y = (y => y)(); }", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ("const [x = y => y] = [].map(y => y)", None, None, None), // { "ecmaVersion": 6 }, + ("function foo any>(arg: T) {}", None, None, None), + ("function foo any>(arg: T) {}", None, None, None), + ("function foo any>(arg: T) {}", None, None, None), + ("function foo any>(fn: T, args: any[]) {}", None, None, None), + ( + "function foo any>(fn: T, args: any[]) {}", + None, + None, + None, + ), + ( + "function foo any>(fn: T, ...args: any[]) {}", + None, + None, + None, + ), + ("function foo any>(fn: T, args: any[]) {}", None, None, None), + ( + "function foo any>(fn: T, args: any[]) {}", + None, + None, + None, + ), + ( + "function foo any>(fn: T, args: any) {}", + None, + None, + None, + ), + ( + " + function foo any>( + fn: T, + ...args: any[] + ) {} + ", + None, + None, + None, + ), + ( + " + type Args = 1; + function foo void>(arg: T) {} + ", + None, + None, + None, + ), + ( + " + export type ArrayInput = Func extends (arg0: Array) => any + ? T[] + : Func extends (...args: infer T) => any + ? T + : never; + ", + None, + None, + None, + ), + ( + " + function foo() { + var Object = 0; + } + ", + None, + None, + None, + ), + ( + " + function test(this: Foo) { + function test2(this: Bar) {} + } + ", + None, + None, + None, + ), + ( + " + class Foo { + prop = 1; + } + namespace Foo { + export const v = 2; + } + ", + None, + None, + None, + ), + ( + " + function Foo() {} + namespace Foo { + export const v = 2; + } + ", + None, + None, + None, + ), + ( + " + class Foo { + prop = 1; + } + interface Foo { + prop2: string; + } + ", + None, + None, + None, + ), + ( + " + import type { Foo } from 'bar'; + + declare module 'bar' { + export interface Foo { + x: string; + } + } + ", + None, + None, + None, + ), + ( + " + const x = 1; + type x = string; + ", + None, + None, + None, + ), + ( + " + const x = 1; + { + type x = string; + } + ", + None, + None, + None, + ), + ( + " + type Foo = 1; + ", + Some(serde_json::json!([{ "ignoreTypeValueShadow": true }])), + None, + None, + ), // { "globals": { "Foo": "writable", }, }, + ( + " + type Foo = 1; + ", + Some( + serde_json::json!([ { "builtinGlobals": false, "ignoreTypeValueShadow": false, }, ]), + ), + None, + None, + ), // { "globals": { "Foo": "writable", }, }, + ( + " + enum Direction { + left = 'left', + right = 'right', + } + ", + None, + None, + None, + ), + ( + " + const test = 1; + type Fn = (test: string) => typeof test; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + type Fn = (Foo: string) => typeof Foo; + ", + Some( + serde_json::json!([ { "builtinGlobals": false, "ignoreFunctionTypeParameterNameValueShadow": true, }, ]), + ), + None, + None, + ), // { "globals": { "Foo": "writable", }, }, + ( + " + const arg = 0; + + interface Test { + (arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + const arg = 0; + + interface Test { + p1(arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + const arg = 0; + + declare function test(arg: string): typeof arg; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + const arg = 0; + + declare const test: (arg: string) => typeof arg; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + const arg = 0; + + declare class Test { + p1(arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + const arg = 0; + + declare const Test: { + new (arg: string): typeof arg; + }; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + const arg = 0; + + type Bar = new (arg: number) => typeof arg; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + const arg = 0; + + declare namespace Lib { + function test(arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + declare global { + interface ArrayConstructor {} + } + export {}; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + None, + ), + ( + " + declare global { + const a: string; + + namespace Foo { + const a: number; + } + } + export {}; + ", + None, + None, + None, + ), + ( + " + declare global { + type A = 'foo'; + + namespace Foo { + type A = 'bar'; + } + } + export {}; + ", + Some(serde_json::json!([{ "ignoreTypeValueShadow": false }])), + None, + None, + ), + ( + " + declare global { + const foo: string; + type Fn = (foo: number) => void; + } + export {}; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + export class Wrapper { + private constructor(private readonly wrapped: Wrapped) {} + + unwrap(): Wrapped { + return this.wrapped; + } + + static create(wrapped: Wrapped) { + return new Wrapper(wrapped); + } + } + ", + None, + None, + None, + ), + ( + " + function makeA() { + return class A { + constructor(public value: T) {} + + static make(value: T) { + return new A(value); + } + }; + } + ", + None, + None, + None, + ), + ( + " + import type { foo } from './foo'; + type bar = number; + + // 'foo' is already declared in the upper scope + // 'bar' is fine + function doThing(foo: number, bar: number) {} + ", + Some(serde_json::json!([{ "ignoreTypeValueShadow": true }])), + None, + None, + ), + ( + " + import { type foo } from './foo'; + + // 'foo' is already declared in the upper scope + function doThing(foo: number) {} + ", + Some(serde_json::json!([{ "ignoreTypeValueShadow": true }])), + None, + None, + ), + ( + "const a = [].find(a => a);", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + " + const a = [].find(function (a) { + return a; + }); + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const [a = [].find(a => true)] = dummy;", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const { a = [].find(a => true) } = dummy;", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "function func(a = [].find(a => true)) {}", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + " + for (const a in [].find(a => true)) { + } + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + " + for (const a of [].find(a => true)) { + } + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const a = [].map(a => true).filter(a => a === 'b');", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + " + const a = [] + .map(a => true) + .filter(a => a === 'b') + .find(a => a === 'c'); + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const { a } = (({ a }) => ({ a }))();", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + " + const person = people.find(item => { + const person = item.name; + return person === 'foo'; + }); + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "var y = bar || foo(y => y);", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "var y = bar && foo(y => y);", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "var z = bar(foo(z => z));", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "var z = boo(bar(foo(z => z)));", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + " + var match = function (person) { + return person.name === 'foo'; + }; + const person = [].find(match); + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const a = foo(x || (a => {}));", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const { a = 1 } = foo(a => {});", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const person = { ...people.find(person => person.firstName.startsWith('s')) };", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "parserOptions": { "ecmaVersion": 2021 } }, + ( + " + const person = { + firstName: people + .filter(person => person.firstName.startsWith('s')) + .map(person => person.firstName)[0], + }; + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "parserOptions": { "ecmaVersion": 2021 } }, + ( + " + () => { + const y = foo(y => y); + }; + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const x = (x => x)();", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "var y = bar || (y => y)();", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "var y = bar && (y => y)();", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "var x = (x => x)((y => y)());", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const { a = 1 } = (a => {})();", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + " + () => { + const y = (y => y)(); + }; + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ("const [x = y => y] = [].map(y => y);", None, None, None), + ( + " + type Foo = 1; + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), + ( + " + interface Foo {} + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), + ( + " + interface Foo {} + interface A {} + ", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), + ( + " + type Foo = 1; + interface A {} + ", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), + ( + " + { + type A = 1; + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), + ( + " + { + interface Foo {} + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), + ( + " + type Foo = 1; + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "functions" }])), + None, + None, + ), + ( + " + interface Foo {} + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "functions" }])), + None, + None, + ), + ( + " + interface Foo {} + interface A {} + ", + Some(serde_json::json!([{ "hoist": "functions" }])), + None, + None, + ), + ( + " + type Foo = 1; + interface A {} + ", + Some(serde_json::json!([{ "hoist": "functions" }])), + None, + None, + ), + ( + " + { + type A = 1; + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "functions" }])), + None, + None, + ), + ( + " + { + interface Foo {} + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "functions" }])), + None, + None, + ), + ( + " + import type { Foo } from 'bar'; + + declare module 'bar' { + export type Foo = string; + } + ", + None, + None, + None, + ), + ( + " + import type { Foo } from 'bar'; + + declare module 'bar' { + interface Foo { + x: string; + } + } + ", + None, + None, + None, + ), + ( + " + import { type Foo } from 'bar'; + + declare module 'bar' { + export type Foo = string; + } + ", + None, + None, + None, + ), + ( + " + import { type Foo } from 'bar'; + + declare module 'bar' { + export interface Foo { + x: string; + } + } + ", + None, + None, + None, + ), + ( + " + import { type Foo } from 'bar'; + + declare module 'bar' { + type Foo = string; + } + ", + None, + None, + None, + ), + ( + " + import { type Foo } from 'bar'; + + declare module 'bar' { + interface Foo { + x: string; + } + } + ", + None, + None, + None, + ), + ( + " + declare const foo1: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "foo1": false, }, }, + ( + " + declare let foo2: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "foo2": false, }, }, + ( + " + declare var foo3: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "foo3": false, }, }, + ( + " + function foo4(name: string): void; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "foo4": false, }, }, + ( + " + declare class Foopy1 { + name: string; + } + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "Foopy1": false, }, }, + ( + " + declare interface Foopy2 { + name: string; + } + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "Foopy2": false, }, }, + ( + " + declare type Foopy3 = { + x: number; + }; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "Foopy3": false, }, }, + ( + " + declare enum Foopy4 { + x, + } + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "Foopy4": false, }, }, + ( + " + declare namespace Foopy5 {} + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "Foopy5": false, }, }, + ( + " + declare; + foo5: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "foo5": false, }, }, + ( + " + declare const foo1: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.cts")), + ), // { "globals": { "foo1": false, }, }, + ( + " + declare let foo2: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.cts")), + ), // { "globals": { "foo2": false, }, }, + ( + " + declare var foo3: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.cts")), + ), // { "globals": { "foo3": false, }, }, + ( + " + function foo4(name: string): void; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.cts")), + ), // { "globals": { "foo4": false, }, }, + ( + " + declare class Foopy1 { + name: string; + } + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.cts")), + ), // { "globals": { "Foopy1": false, }, }, + ( + " + declare interface Foopy2 { + name: string; + } + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.cts")), + ), // { "globals": { "Foopy2": false, }, }, + ( + " + declare type Foopy3 = { + x: number; + }; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.cts")), + ), // { "globals": { "Foopy3": false, }, }, + ( + " + declare enum Foopy4 { + x, + } + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.cts")), + ), // { "globals": { "Foopy4": false, }, }, + ( + " + declare namespace Foopy5 {} + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.cts")), + ), // { "globals": { "Foopy5": false, }, }, + ( + " + declare; + foo5: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.cts")), + ), // { "globals": { "foo5": false, }, }, + ( + " + declare const foo1: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.mts")), + ), // { "globals": { "foo1": false, }, }, + ( + " + declare let foo2: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.mts")), + ), // { "globals": { "foo2": false, }, }, + ( + " + declare var foo3: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.mts")), + ), // { "globals": { "foo3": false, }, }, + ( + " + function foo4(name: string): void; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.mts")), + ), // { "globals": { "foo4": false, }, }, + ( + " + declare class Foopy1 { + name: string; + } + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.mts")), + ), // { "globals": { "Foopy1": false, }, }, + ( + " + declare interface Foopy2 { + name: string; + } + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.mts")), + ), // { "globals": { "Foopy2": false, }, }, + ( + " + declare type Foopy3 = { + x: number; + }; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.mts")), + ), // { "globals": { "Foopy3": false, }, }, + ( + " + declare enum Foopy4 { + x, + } + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.mts")), + ), // { "globals": { "Foopy4": false, }, }, + ( + " + declare namespace Foopy5 {} + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.mts")), + ), // { "globals": { "Foopy5": false, }, }, + ( + " + declare; + foo5: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.mts")), + ), // { "globals": { "foo5": false, }, } + ]; + + let fail = vec![ + ("function a(x) { var b = function c() { var x = 'foo'; }; }", None, None, None), + ("var a = (x) => { var b = () => { var x = 'foo'; }; }", None, None, None), // { "ecmaVersion": 6 }, + ("function a(x) { var b = function () { var x = 'foo'; }; }", None, None, None), + ("var x = 1; function a(x) { return ++x; }", None, None, None), + ("var a=3; function b() { var a=10; }", None, None, None), + ( + "var a=3; function b() { var a=10; }; setTimeout(function() { b(); }, 0);", + None, + None, + None, + ), + ( + "var a=3; function b() { var a=10; var b=0; }; setTimeout(function() { b(); }, 0);", + None, + None, + None, + ), + ("var x = 1; { let x = 2; }", None, None, None), // { "ecmaVersion": 6 }, + ("let x = 1; { const x = 2; }", None, None, None), // { "ecmaVersion": 6 }, + ("{ let a; } function a() {}", None, None, None), // { "ecmaVersion": 6 }, + ("{ const a = 0; } function a() {}", None, None, None), // { "ecmaVersion": 6 }, + ("function foo() { let a; } function a() {}", None, None, None), // { "ecmaVersion": 6 }, + ("function foo() { var a; } function a() {}", None, None, None), // { "ecmaVersion": 6 }, + ("function foo(a) { } function a() {}", None, None, None), // { "ecmaVersion": 6 }, + ("{ let a; } let a;", Some(serde_json::json!([{ "hoist": "all" }])), None, None), // { "ecmaVersion": 6 }, + ("{ let a; } var a;", Some(serde_json::json!([{ "hoist": "all" }])), None, None), // { "ecmaVersion": 6 }, + ("{ let a; } function a() {}", Some(serde_json::json!([{ "hoist": "all" }])), None, None), // { "ecmaVersion": 6 }, + ( + "{ const a = 0; } const a = 1;", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ("{ const a = 0; } var a;", Some(serde_json::json!([{ "hoist": "all" }])), None, None), // { "ecmaVersion": 6 }, + ( + "{ const a = 0; } function a() {}", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo() { let a; } let a;", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo() { let a; } var a;", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo() { let a; } function a() {}", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo() { var a; } let a;", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo() { var a; } var a;", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function foo() { var a; } function a() {}", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ("function foo(a) { } let a;", Some(serde_json::json!([{ "hoist": "all" }])), None, None), // { "ecmaVersion": 6 }, + ("function foo(a) { } var a;", Some(serde_json::json!([{ "hoist": "all" }])), None, None), // { "ecmaVersion": 6 }, + ( + "function foo(a) { } function a() {}", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ("(function a() { function a(){} })()", None, None, None), + ("(function a() { class a{} })()", None, None, None), // { "ecmaVersion": 6 }, + ("(function a() { (function a(){}); })()", None, None, None), + ("(function a() { (class a{}); })()", None, None, None), // { "ecmaVersion": 6 }, + ("(function() { var a = function(a) {}; })()", None, None, None), + ("(function() { var a = function() { function a() {} }; })()", None, None, None), + ("(function() { var a = function() { class a{} }; })()", None, None, None), // { "ecmaVersion": 6 }, + ("(function() { var a = function() { (function a() {}); }; })()", None, None, None), + ("(function() { var a = function() { (class a{}); }; })()", None, None, None), // { "ecmaVersion": 6 }, + ("(function() { var a = class { constructor() { class a {} } }; })()", None, None, None), // { "ecmaVersion": 6 }, + ("class A { constructor() { var A; } }", None, None, None), // { "ecmaVersion": 6 }, + ("(function a() { function a(){ function a(){} } })()", None, None, None), + ( + "function foo() { var Object = 0; }", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + None, + ), + ( + "function foo() { var top = 0; }", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + None, + ), // { "globals": globals.browser }, + ("var Object = 0;", Some(serde_json::json!([{ "builtinGlobals": true }])), None, None), // { "ecmaVersion": 6, "sourceType": "module" }, + ("var top = 0;", Some(serde_json::json!([{ "builtinGlobals": true }])), None, None), // { "ecmaVersion": 6, "sourceType": "module", "globals": globals.browser, }, + ("var Object = 0;", Some(serde_json::json!([{ "builtinGlobals": true }])), None, None), // { "parserOptions": { "ecmaFeatures": { "globalReturn": true } }, }, + ("var top = 0;", Some(serde_json::json!([{ "builtinGlobals": true }])), None, None), // { "parserOptions": { "ecmaFeatures": { "globalReturn": true } }, "globals": globals.browser, }, + ("function foo(cb) { (function (cb) { cb(42); })(cb); }", None, None, None), + ("class C { static { let a; { let a; } } }", None, None, None), // { "ecmaVersion": 2022 }, + ("class C { static { var C; } }", None, None, None), // { "ecmaVersion": 2022 }, + ("class C { static { let C; } }", None, None, None), // { "ecmaVersion": 2022 }, + ("var a; class C { static { var a; } }", None, None, None), // { "ecmaVersion": 2022 }, + ( + "class C { static { var a; } } var a;", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "ecmaVersion": 2022 }, + ( + "class C { static { let a; } } let a;", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "ecmaVersion": 2022 }, + ( + "class C { static { var a; } } let a;", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "ecmaVersion": 2022 }, + ("class C { static { var a; class D { static { var a; } } } }", None, None, None), // { "ecmaVersion": 2022 }, + ("class C { static { let a; class D { static { let a; } } } }", None, None, None), // { "ecmaVersion": 2022 }, + ( + "let x = foo((x,y) => {}); + let y;", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const a = fn(()=>{ class C { fn () { const a = 42; return a } } return new C() })", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "function a() {} + foo(a => {});", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const a = fn(()=>{ function C() { this.fn=function() { const a = 42; return a } } return new C() });", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const x = foo(() => { const bar = () => { return x => {}; }; return bar; });", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const x = foo(() => { return { bar(x) {} }; });", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const x = () => { foo(x => x); }", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const foo = () => { let x; bar(x => x); }", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "foo(() => { const x = x => x; });", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const foo = (x) => { bar(x => {}) }", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "let x = ((x,y) => {})(); + let y;", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const a = (()=>{ class C { fn () { const a = 42; return a } } return new C() })()", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "const x = () => { (x => x)(); }", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "ecmaVersion": 6 }, + ( + "let x = false; export const a = wrap(function a() { if (!x) { x = true; a(); } });", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "ecmaVersion": 6, "sourceType": "module" }, + ("const a = wrap(function a() {});", None, None, None), // { "ecmaVersion": 6 }, + ("const a = foo || wrap(function a() {});", None, None, None), // { "ecmaVersion": 6 }, + ("const { a = wrap(function a() {}) } = obj;", None, None, None), // { "ecmaVersion": 6 }, + ("const { a = foo || wrap(function a() {}) } = obj;", None, None, None), // { "ecmaVersion": 6 }, + ("const { a = foo, b = function a() {} } = {}", None, None, None), // { "ecmaVersion": 6 }, + ("const { A = Foo, B = class A {} } = {}", None, None, None), // { "ecmaVersion": 6 }, + ("function foo(a = wrap(function a() {})) {}", None, None, None), // { "ecmaVersion": 6 }, + ("function foo(a = foo || wrap(function a() {})) {}", None, None, None), // { "ecmaVersion": 6 }, + ("const A = wrap(class A {});", None, None, None), // { "ecmaVersion": 6 }, + ("const A = foo || wrap(class A {});", None, None, None), // { "ecmaVersion": 6 }, + ("const { A = wrap(class A {}) } = obj;", None, None, None), // { "ecmaVersion": 6 }, + ("const { A = foo || wrap(class A {}) } = obj;", None, None, None), // { "ecmaVersion": 6 }, + ("function foo(A = wrap(class A {})) {}", None, None, None), // { "ecmaVersion": 6 }, + ("function foo(A = foo || wrap(class A {})) {}", None, None, None), // { "ecmaVersion": 6 }, + ("var a = function a() {} ? foo : bar", None, None, None), + ("var A = class A {} ? foo : bar", None, None, None), // { "ecmaVersion": 6, }, + ( + "(function Array() {})", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + None, + ), // { "ecmaVersion": 6, "sourceType": "module", }, + ("let a; { let b = (function a() {}) }", None, None, None), // { "ecmaVersion": 6, }, + ("let a = foo; { let b = (function a() {}) }", None, None, None), // { "ecmaVersion": 6, }, + ( + " + type T = 1; + { + type T = 2; + } + ", + None, + None, + None, + ), + ( + " + type T = 1; + function foo(arg: T) {} + ", + None, + None, + None, + ), + ( + " + function foo() { + return function () {}; + } + ", + None, + None, + None, + ), + ( + " + type T = string; + function foo void>(arg: T) {} + ", + None, + None, + None, + ), + ( + " + const x = 1; + { + type x = string; + } + ", + Some(serde_json::json!([{ "ignoreTypeValueShadow": false }])), + None, + None, + ), + ( + " + type Foo = 1; + ", + Some( + serde_json::json!([ { "builtinGlobals": true, "ignoreTypeValueShadow": false, }, ]), + ), + None, + None, + ), // { "globals": { "Foo": "writable", }, }, + ( + " + const test = 1; + type Fn = (test: string) => typeof test; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + type Fn = (Foo: string) => typeof Foo; + ", + Some( + serde_json::json!([ { "builtinGlobals": true, "ignoreFunctionTypeParameterNameValueShadow": false, }, ]), + ), + None, + None, + ), // { "globals": { "Foo": "writable", }, }, + ( + " + const arg = 0; + + interface Test { + (arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + const arg = 0; + + interface Test { + p1(arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + const arg = 0; + + declare function test(arg: string): typeof arg; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + const arg = 0; + + declare const test: (arg: string) => typeof arg; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + const arg = 0; + + declare class Test { + p1(arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + const arg = 0; + + declare const Test: { + new (arg: string): typeof arg; + }; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + const arg = 0; + + type Bar = new (arg: number) => typeof arg; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + const arg = 0; + + declare namespace Lib { + function test(arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + import type { foo } from './foo'; + function doThing(foo: number) {} + ", + Some(serde_json::json!([{ "ignoreTypeValueShadow": false }])), + None, + None, + ), + ( + " + import { type foo } from './foo'; + function doThing(foo: number) {} + ", + Some(serde_json::json!([{ "ignoreTypeValueShadow": false }])), + None, + None, + ), + ( + " + import { foo } from './foo'; + function doThing(foo: number, bar: number) {} + ", + Some(serde_json::json!([{ "ignoreTypeValueShadow": true }])), + None, + None, + ), + ( + " + interface Foo {} + + declare module 'bar' { + export interface Foo { + x: string; + } + } + ", + None, + None, + None, + ), + ( + " + import type { Foo } from 'bar'; + + declare module 'baz' { + export interface Foo { + x: string; + } + } + ", + None, + None, + None, + ), + ( + " + import { type Foo } from 'bar'; + + declare module 'baz' { + export interface Foo { + x: string; + } + } + ", + None, + None, + None, + ), + ( + " + let x = foo((x, y) => {}); + let y; + ", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "parserOptions": { "ecmaVersion": 6 } }, + ( + " + let x = foo((x, y) => {}); + let y; + ", + Some(serde_json::json!([{ "hoist": "functions" }])), + None, + None, + ), // { "parserOptions": { "ecmaVersion": 6 } }, + ( + " + type Foo = 1; + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "types" }])), + None, + None, + ), + ( + " + interface Foo {} + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "types" }])), + None, + None, + ), + ( + " + interface Foo {} + interface A {} + ", + Some(serde_json::json!([{ "hoist": "types" }])), + None, + None, + ), + ( + " + type Foo = 1; + interface A {} + ", + Some(serde_json::json!([{ "hoist": "types" }])), + None, + None, + ), + ( + " + { + type A = 1; + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "types" }])), + None, + None, + ), + ( + " + { + interface A {} + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "types" }])), + None, + None, + ), + ( + " + type Foo = 1; + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), + ( + " + interface Foo {} + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), + ( + " + interface Foo {} + interface A {} + ", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), + ( + " + type Foo = 1; + interface A {} + ", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), + ( + " + { + type A = 1; + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), + ( + " + { + interface A {} + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), + ( + " + type Foo = 1; + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "functions-and-types" }])), + None, + None, + ), + ( + " + if (true) { + const foo = 6; + } + + function foo() { } + ", + Some(serde_json::json!([{ "hoist": "functions-and-types" }])), + None, + None, + ), + ( + " + // types + type Bar = 1; + type Foo = 1; + + // functions + if (true) { + const b = 6; + } + + function b() { } + ", + Some(serde_json::json!([{ "hoist": "functions-and-types" }])), + None, + None, + ), + ( + " + // types + type Bar = 1; + type Foo = 1; + + // functions + if (true) { + const b = 6; + } + + function b() { } + ", + Some(serde_json::json!([{ "hoist": "types" }])), + None, + None, + ), + ( + " + interface Foo {} + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "functions-and-types" }])), + None, + None, + ), + ( + " + interface Foo {} + interface A {} + ", + Some(serde_json::json!([{ "hoist": "functions-and-types" }])), + None, + None, + ), + ( + " + type Foo = 1; + interface A {} + ", + Some(serde_json::json!([{ "hoist": "functions-and-types" }])), + None, + None, + ), + ( + " + { + type A = 1; + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "functions-and-types" }])), + None, + None, + ), + ( + " + { + interface A {} + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "functions-and-types" }])), + None, + None, + ), + ( + " + function foo any>(fn: T, args: any[]) {} + ", + Some( + serde_json::json!([ { "builtinGlobals": true, "ignoreTypeValueShadow": false, }, ]), + ), + None, + None, + ), // { "globals": { "args": "writable", }, }, + ( + " + declare const has = (environment: 'dev' | 'prod' | 'test') => boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + None, + ), // { "globals": { "has": false, }, }, + ( + " + declare const has: (environment: 'dev' | 'prod' | 'test') => boolean; + const fn = (has: string) => {}; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("foo.d.ts")), + ), // { "globals": { "has": false, }, }, + ( + " + const A = 2; + enum Test { + A = 1, + B = A, + } + ", + None, + None, + None, + ), + ]; + + Tester::new(NoShadow::NAME, NoShadow::PLUGIN, pass, fail).test_and_snapshot(); +} + +#[test] +fn test_typescript_eslint() { + use crate::tester::Tester; + use std::path::PathBuf; + let pass = vec![ - // Different names - no shadowing - ("var x = 1; function foo() { var y = 2; }", None), - // Same name in different functions - no shadowing - ("function foo(x) { } function bar(x) { }", None), - // Type vs value with same name (TypeScript) - default ignored - ("type Foo = string; const Foo = 'bar';", None), - // Interface vs value with same name - ("interface Foo { x: number } const Foo = { x: 1 };", None), - // Type parameter shadowing value (default ignored) - ("const T = 1; function foo() { }", None), - // Enum member doesn't shadow - ("const Red = 1; enum Color { Red, Green, Blue }", None), - // Class with same name as type (declaration merging) - ("interface Foo { x: number } class Foo { x = 1; }", None), - // Namespace with same name as class - ("class Foo { } namespace Foo { export const x = 1; }", None), - // Import used only as type, value with same name - ("import { Foo } from './foo'; type X = Foo; const Foo = 1;", None), - // Allowed names - ("var x = 1; function foo() { var x = 2; }", Some(serde_json::json!([{ "allow": ["x"] }]))), - // Reassign - ("let x = true; if (x) { x = false; }", Some(serde_json::json!([{ "allow": ["x"] }]))), - // --- hoist = never: do NOT report if the outer declaration happens later --- - ( - "function f() { { let x = 1; } let x = 2; }", - Some(serde_json::json!([{ "hoist": "never" }])), - ), - // hoist = never: even if the outer is a function declaration, "never" should NOT report when it appears later - ( - "function f() { { let x = 1; } function x() {} }", - Some(serde_json::json!([{ "hoist": "never" }])), - ), - // --- hoist = functions: do NOT report if the outer declaration happens later and it is NOT a function declaration --- - ( - "function f() { { let x = 1; } var x = 2; }", + ("function foo any>(arg: T) {}", None, None, None), + ("function foo any>(arg: T) {}", None, None, None), + ("function foo any>(arg: T) {}", None, None, None), + ("function foo any>(fn: T, args: any[]) {}", None, None, None), + ( + "function foo any>(fn: T, args: any[]) {}", + None, + None, + None, + ), + ( + "function foo any>(fn: T, ...args: any[]) {}", + None, + None, + None, + ), + ("function foo any>(fn: T, args: any[]) {}", None, None, None), + ( + "function foo any>(fn: T, args: any[]) {}", + None, + None, + None, + ), + ( + "function foo any>(fn: T, args: any) {}", + None, + None, + None, + ), + ( + " + function foo any>( + fn: T, + ...args: any[] + ) {} + ", + None, + None, + None, + ), + ( + " + type Args = 1; + function foo void>(arg: T) {} + ", + None, + None, + None, + ), + ( + " + export type ArrayInput = Func extends (arg0: Array) => any + ? T[] + : Func extends (...args: infer T) => any + ? T + : never; + ", + None, + None, + None, + ), + ( + " + function foo() { + var Object = 0; + } + ", + None, + None, + None, + ), + ( + " + function test(this: Foo) { + function test2(this: Bar) {} + } + ", + None, + None, + None, + ), + ( + " + class Foo { + prop = 1; + } + namespace Foo { + export const v = 2; + } + ", + None, + None, + None, + ), + ( + " + function Foo() {} + namespace Foo { + export const v = 2; + } + ", + None, + None, + None, + ), + ( + " + class Foo { + prop = 1; + } + interface Foo { + prop2: string; + } + ", + None, + None, + None, + ), + ( + " + import type { Foo } from 'bar'; + + declare module 'bar' { + export interface Foo { + x: string; + } + } + ", + None, + None, + None, + ), + ( + " + const x = 1; + type x = string; + ", + None, + None, + None, + ), + ( + " + const x = 1; + { + type x = string; + } + ", + None, + None, + None, + ), + ( + " + type Foo = 1; + ", + Some(serde_json::json!([{ "ignoreTypeValueShadow": true }])), + None, + None, + ), // { "globals": { "Foo": "writable", }, }, + ( + " + type Foo = 1; + ", + Some( + serde_json::json!([ { "builtinGlobals": false, "ignoreTypeValueShadow": false, }, ]), + ), + None, + None, + ), // { "globals": { "Foo": "writable", }, }, + ( + " + enum Direction { + left = 'left', + right = 'right', + } + ", + None, + None, + None, + ), + ( + " + const test = 1; + type Fn = (test: string) => typeof test; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + type Fn = (Foo: string) => typeof Foo; + ", + Some( + serde_json::json!([ { "builtinGlobals": false, "ignoreFunctionTypeParameterNameValueShadow": true, }, ]), + ), + None, + None, + ), // { "globals": { "Foo": "writable", }, }, + ( + " + const arg = 0; + + interface Test { + (arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + const arg = 0; + + interface Test { + p1(arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + const arg = 0; + + declare function test(arg: string): typeof arg; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + const arg = 0; + + declare const test: (arg: string) => typeof arg; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + const arg = 0; + + declare class Test { + p1(arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + const arg = 0; + + declare const Test: { + new (arg: string): typeof arg; + }; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + const arg = 0; + + type Bar = new (arg: number) => typeof arg; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + const arg = 0; + + declare namespace Lib { + function test(arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": true }])), + None, + None, + ), + ( + " + declare global { + interface ArrayConstructor {} + } + export {}; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + None, + ), + ( + " + declare global { + const a: string; + + namespace Foo { + const a: number; + } + } + export {}; + ", + None, + None, + None, + ), + ( + " + declare global { + type A = 'foo'; + + namespace Foo { + type A = 'bar'; + } + } + export {}; + ", + Some(serde_json::json!([{ "ignoreTypeValueShadow": false }])), + None, + None, + ), + ( + " + declare global { + const foo: string; + type Fn = (foo: number) => void; + } + export {}; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + export class Wrapper { + private constructor(private readonly wrapped: Wrapped) {} + + unwrap(): Wrapped { + return this.wrapped; + } + + static create(wrapped: Wrapped) { + return new Wrapper(wrapped); + } + } + ", + None, + None, + None, + ), + ( + " + function makeA() { + return class A { + constructor(public value: T) {} + + static make(value: T) { + return new A(value); + } + }; + } + ", + None, + None, + None, + ), + ( + " + import type { foo } from './foo'; + type bar = number; + + // 'foo' is already declared in the upper scope + // 'bar' is fine + function doThing(foo: number, bar: number) {} + ", + Some(serde_json::json!([{ "ignoreTypeValueShadow": true }])), + None, + None, + ), + ( + " + import { type foo } from './foo'; + + // 'foo' is already declared in the upper scope + function doThing(foo: number) {} + ", + Some(serde_json::json!([{ "ignoreTypeValueShadow": true }])), + None, + None, + ), + ( + "const a = [].find(a => a);", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + " + const a = [].find(function (a) { + return a; + }); + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const [a = [].find(a => true)] = dummy;", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const { a = [].find(a => true) } = dummy;", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "function func(a = [].find(a => true)) {}", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + " + for (const a in [].find(a => true)) { + } + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + " + for (const a of [].find(a => true)) { + } + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const a = [].map(a => true).filter(a => a === 'b');", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + " + const a = [] + .map(a => true) + .filter(a => a === 'b') + .find(a => a === 'c'); + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const { a } = (({ a }) => ({ a }))();", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + " + const person = people.find(item => { + const person = item.name; + return person === 'foo'; + }); + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "var y = bar || foo(y => y);", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "var y = bar && foo(y => y);", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "var z = bar(foo(z => z));", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "var z = boo(bar(foo(z => z)));", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + " + var match = function (person) { + return person.name === 'foo'; + }; + const person = [].find(match); + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const a = foo(x || (a => {}));", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const { a = 1 } = foo(a => {});", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const person = { ...people.find(person => person.firstName.startsWith('s')) };", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "parserOptions": { "ecmaVersion": 2021 } }, + ( + " + const person = { + firstName: people + .filter(person => person.firstName.startsWith('s')) + .map(person => person.firstName)[0], + }; + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), // { "parserOptions": { "ecmaVersion": 2021 } }, + ( + " + () => { + const y = foo(y => y); + }; + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const x = (x => x)();", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "var y = bar || (y => y)();", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "var y = bar && (y => y)();", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "var x = (x => x)((y => y)());", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + "const { a = 1 } = (a => {})();", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ( + " + () => { + const y = (y => y)(); + }; + ", + Some(serde_json::json!([{ "ignoreOnInitialization": true }])), + None, + None, + ), + ("const [x = y => y] = [].map(y => y);", None, None, None), + ( + " + type Foo = 1; + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), + ( + " + interface Foo {} + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), + ( + " + interface Foo {} + interface A {} + ", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), + ( + " + type Foo = 1; + interface A {} + ", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), + ( + " + { + type A = 1; + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), + ( + " + { + interface Foo {} + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "never" }])), + None, + None, + ), + ( + " + type Foo = 1; + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "functions" }])), + None, + None, + ), + ( + " + interface Foo {} + type A = 1; + ", Some(serde_json::json!([{ "hoist": "functions" }])), + None, + None, ), ( - "function f() { { let C = 1; } class C {} }", + " + interface Foo {} + interface A {} + ", Some(serde_json::json!([{ "hoist": "functions" }])), + None, + None, ), - // --- allow: should suppress the diagnostic regardless of hoist setting --- ( - "function f() { { let x = 1; } let x = 2; }", - Some(serde_json::json!([{ "hoist": "all", "allow": ["x"] }])), + " + type Foo = 1; + interface A {} + ", + Some(serde_json::json!([{ "hoist": "functions" }])), + None, + None, ), - // allow multiple names ( - "let x = 1; function f(){ let x = 2; } let y = 1; function g(){ let y = 2; }", - Some(serde_json::json!([{ "allow": ["x", "y"] }])), + " + { + type A = 1; + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "functions" }])), + None, + None, ), - // allow applied to destructuring (you already have the failing version; this ensures the escape hatch works) - ("const x = 1; { const { x } = { x: 2 }; }", Some(serde_json::json!([{ "allow": ["x"] }]))), - // Outer is NOT a function declaration; it's a const variable initialized with a function expression. ( - "function f() { { let x = 1; } const x = function() {}; }", + " + { + interface Foo {} + } + type A = 1; + ", Some(serde_json::json!([{ "hoist": "functions" }])), + None, + None, + ), + ( + " + import type { Foo } from 'bar'; + + declare module 'bar' { + export type Foo = string; + } + ", + None, + None, + None, + ), + ( + " + import type { Foo } from 'bar'; + + declare module 'bar' { + interface Foo { + x: string; + } + } + ", + None, + None, + None, + ), + ( + " + import { type Foo } from 'bar'; + + declare module 'bar' { + export type Foo = string; + } + ", + None, + None, + None, + ), + ( + " + import { type Foo } from 'bar'; + + declare module 'bar' { + export interface Foo { + x: string; + } + } + ", + None, + None, + None, ), - // ------------------------- - // TypeScript specific pass cases - // ------------------------- - // `allow` should also override type/value behavior even when ignoreTypeValueShadow=false. ( - "type Foo = string; { const Foo = 'bar'; }", - Some(serde_json::json!([{ - "ignoreTypeValueShadow": false, - "allow": ["Foo"] - }])), + " + import { type Foo } from 'bar'; + + declare module 'bar' { + type Foo = string; + } + ", + None, + None, + None, ), - // If ignoreTypeValueShadow=false, this would normally be reportable... - // ...but with ignoreFunctionTypeParameterNameValueShadow=true it must be ignored (pass). ( - "const T = 1; function foo() { }", - Some(serde_json::json!([{ - "ignoreTypeValueShadow": false, - "ignoreFunctionTypeParameterNameValueShadow": true - }])), + " + import { type Foo } from 'bar'; + + declare module 'bar' { + interface Foo { + x: string; + } + } + ", + None, + None, + None, ), + ( + " + declare const foo1: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "foo1": false, }, }, + ( + " + declare let foo2: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "foo2": false, }, }, + ( + " + declare var foo3: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "foo3": false, }, }, + ( + " + function foo4(name: string): void; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "foo4": false, }, }, + ( + " + declare class Foopy1 { + name: string; + } + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "Foopy1": false, }, }, + ( + " + declare interface Foopy2 { + name: string; + } + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "Foopy2": false, }, }, + ( + " + declare type Foopy3 = { + x: number; + }; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "Foopy3": false, }, }, + ( + " + declare enum Foopy4 { + x, + } + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "Foopy4": false, }, }, + ( + " + declare namespace Foopy5 {} + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "Foopy5": false, }, }, + ( + " + declare; + foo5: boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("baz.d.ts")), + ), // { "globals": { "foo5": false, }, } ]; let fail = vec![ - // Basic shadowing - ("var x = 1; function foo() { var x = 2; }", None), - // Block scope shadowing - ("const x = 1; { const x = 2; }", None), - // Parameter shadowing outer variable - ("var x = 1; function foo(x) { }", None), - // Nested function shadowing - ("function foo() { var x = 1; function bar() { var x = 2; } }", None), - // Arrow function shadowing - ("const x = 1; const foo = () => { const x = 2; };", None), - // Class method shadowing - ("const x = 1; class Foo { method() { const x = 2; } }", None), - // Let shadowing - ("let x = 1; { let x = 2; }", None), - // Catch clause shadowing - ("const e = 1; try { } catch (e) { }", None), - // For loop variable shadowing - ("const i = 1; for (let i = 0; i < 10; i++) { }", None), - // Destructuring shadowing in nested scope - ("const x = 1; { const { x } = { x: 2 }; }", None), - // Array destructuring shadowing in nested scope - ("const x = 1; { const [x] = [2]; }", None), - ("let x = 1; { { let x = 3; } let x = 2; }", None), - // Type shadowing type (not ignored) - ("type Foo = string; { type Foo = number; }", None), - // Interface shadowing interface - ("interface Foo { x: number } { interface Foo { y: string } }", None), - // --- hoist = all: DO report even if the outer declaration happens later --- - ( - "function f() { { let x = 1; } let x = 2; }", - Some(serde_json::json!([{ "hoist": "all" }])), - ), - // --- hoist = functions: DO report if the shadowed symbol is a function declaration even when it appears later --- - ( - "function f() { { let x = 1; } function x() {} }", - Some(serde_json::json!([{ "hoist": "functions" }])), + ( + " + type T = 1; + { + type T = 2; + } + ", + None, + None, + None, ), - // hoist = all: should also report when the outer is var and appears later (generalized hoisting behavior) ( - "function f() { { let x = 1; } var x = 2; }", - Some(serde_json::json!([{ "hoist": "all" }])), + " + type T = 1; + function foo(arg: T) {} + ", + None, + None, + None, ), - // hoist = never: should still report the normal case (outer first, inner later) ( - "function f() { let x = 2; { let x = 1; } }", - Some(serde_json::json!([{ "hoist": "never" }])), + " + function foo() { + return function () {}; + } + ", + None, + None, + None, + ), + ( + " + type T = string; + function foo void>(arg: T) {} + ", + None, + None, + None, + ), + ( + " + const x = 1; + { + type x = string; + } + ", + Some(serde_json::json!([{ "ignoreTypeValueShadow": false }])), + None, + None, + ), + ( + " + type Foo = 1; + ", + Some( + serde_json::json!([ { "builtinGlobals": true, "ignoreTypeValueShadow": false, }, ]), + ), + None, + None, + ), // { "globals": { "Foo": "writable", }, }, + ( + " + const test = 1; + type Fn = (test: string) => typeof test; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + type Fn = (Foo: string) => typeof Foo; + ", + Some( + serde_json::json!([ { "builtinGlobals": true, "ignoreFunctionTypeParameterNameValueShadow": false, }, ]), + ), + None, + None, + ), // { "globals": { "Foo": "writable", }, }, + ( + " + const arg = 0; + + interface Test { + (arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + const arg = 0; + + interface Test { + p1(arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + const arg = 0; + + declare function test(arg: string): typeof arg; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + const arg = 0; + + declare const test: (arg: string) => typeof arg; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + const arg = 0; + + declare class Test { + p1(arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + const arg = 0; + + declare const Test: { + new (arg: string): typeof arg; + }; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + const arg = 0; + + type Bar = new (arg: number) => typeof arg; + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, ), - // --- allow: allowing only "x" must NOT allow "y" --- - ("let y = 1; function g(){ let y = 2; }", Some(serde_json::json!([{ "allow": ["x"] }]))), - // allow is case-sensitive - ("let x = 1; function f(){ let x = 2; }", Some(serde_json::json!([{ "allow": ["X"] }]))), - // ------------------------- - // TypeScript specific fail cases - // ------------------------- - // ignoreTypeValueShadow = false => type/value with the same name is now reportable ( - "type Foo = string; { const Foo = 'bar'; }", + " + const arg = 0; + + declare namespace Lib { + function test(arg: string): typeof arg; + } + ", + Some(serde_json::json!([{ "ignoreFunctionTypeParameterNameValueShadow": false }])), + None, + None, + ), + ( + " + import type { foo } from './foo'; + function doThing(foo: number) {} + ", Some(serde_json::json!([{ "ignoreTypeValueShadow": false }])), + None, + None, ), ( - "interface Foo { x: number }; { const Foo = { x: 1 } };", + " + import { type foo } from './foo'; + function doThing(foo: number) {} + ", Some(serde_json::json!([{ "ignoreTypeValueShadow": false }])), + None, + None, + ), + ( + " + import { foo } from './foo'; + function doThing(foo: number, bar: number) {} + ", + Some(serde_json::json!([{ "ignoreTypeValueShadow": true }])), + None, + None, + ), + ( + " + interface Foo {} + + declare module 'bar' { + export interface Foo { + x: string; + } + } + ", + None, + None, + None, + ), + ( + " + import type { Foo } from 'bar'; + + declare module 'baz' { + export interface Foo { + x: string; + } + } + ", + None, + None, + None, + ), + ( + " + import { type Foo } from 'bar'; + + declare module 'baz' { + export interface Foo { + x: string; + } + } + ", + None, + None, + None, + ), + ( + " + let x = foo((x, y) => {}); + let y; + ", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), // { "parserOptions": { "ecmaVersion": 6 } }, + ( + " + let x = foo((x, y) => {}); + let y; + ", + Some(serde_json::json!([{ "hoist": "functions" }])), + None, + None, + ), // { "parserOptions": { "ecmaVersion": 6 } }, + ( + " + type Foo = 1; + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "types" }])), + None, + None, + ), + ( + " + interface Foo {} + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "types" }])), + None, + None, + ), + ( + " + interface Foo {} + interface A {} + ", + Some(serde_json::json!([{ "hoist": "types" }])), + None, + None, + ), + ( + " + type Foo = 1; + interface A {} + ", + Some(serde_json::json!([{ "hoist": "types" }])), + None, + None, + ), + ( + " + { + type A = 1; + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "types" }])), + None, + None, + ), + ( + " + { + interface A {} + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "types" }])), + None, + None, + ), + ( + " + type Foo = 1; + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), + ( + " + interface Foo {} + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), + ( + " + interface Foo {} + interface A {} + ", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), + ( + " + type Foo = 1; + interface A {} + ", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), + ( + " + { + type A = 1; + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, + ), + ( + " + { + interface A {} + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "all" }])), + None, + None, ), - // ignoreFunctionTypeParameterNameValueShadow = false - // (and ignoreTypeValueShadow=false to ensure this option is the one deciding) - // Now the function type parameter shadowing a value should be reported. - ( - "const T = 1; function foo() { }", - Some(serde_json::json!([{ - "ignoreTypeValueShadow": false, - "ignoreFunctionTypeParameterNameValueShadow": false - }])), - ), - // Enum shadowing Enum (Enums are values) - ("enum E { A } function foo() { enum E { B } }", None), - // Class shadowing Class (Classes are values) - ("class C { } function foo() { class C { } }", None), - // Import (Value) shadowing Value - // The import is used as a value, so it should be shadowed - ("import { Foo } from './foo'; const x = Foo; function bar() { const Foo = 1; }", None), - // Generic Parameter shadowing Generic Parameter - ("function foo() { function bar() { } }", None), + ( + " + type Foo = 1; + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "functions-and-types" }])), + None, + None, + ), + ( + " + interface Foo {} + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "functions-and-types" }])), + None, + None, + ), + ( + " + interface Foo {} + interface A {} + ", + Some(serde_json::json!([{ "hoist": "functions-and-types" }])), + None, + None, + ), + ( + " + type Foo = 1; + interface A {} + ", + Some(serde_json::json!([{ "hoist": "functions-and-types" }])), + None, + None, + ), + ( + " + { + type A = 1; + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "functions-and-types" }])), + None, + None, + ), + ( + " + { + interface A {} + } + type A = 1; + ", + Some(serde_json::json!([{ "hoist": "functions-and-types" }])), + None, + None, + ), + ( + " + function foo any>(fn: T, args: any[]) {} + ", + Some( + serde_json::json!([ { "builtinGlobals": true, "ignoreTypeValueShadow": false, }, ]), + ), + None, + None, + ), // { "globals": { "args": "writable", }, }, + ( + " + declare const has = (environment: 'dev' | 'prod' | 'test') => boolean; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + None, + ), // { "globals": { "has": false, }, }, + ( + " + declare const has: (environment: 'dev' | 'prod' | 'test') => boolean; + const fn = (has: string) => {}; + ", + Some(serde_json::json!([{ "builtinGlobals": true }])), + None, + Some(PathBuf::from("foo.d.ts")), + ), // { "globals": { "has": false, }, } ]; - Tester::new(NoShadow::NAME, NoShadow::PLUGIN, pass, fail).test_and_snapshot(); + Tester::new(NoShadow::NAME, NoShadow::PLUGIN, pass, fail) + .with_snapshot_suffix("typescript-eslint") + .test_and_snapshot(); } From 89d9cf74e90962bde7850d3b3482956a03d9eb76 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Wed, 11 Feb 2026 13:35:30 +0000 Subject: [PATCH 07/15] compat with original rule --- .../src/rules/eslint/no_shadow/mod.rs | 703 +++++++- .../src/rules/eslint/no_shadow/options.rs | 2 +- .../src/rules/eslint/no_shadow/tests.rs | 18 +- .../src/snapshots/eslint_no_shadow.snap | 1550 +++++++++++++++-- .../eslint_no_shadow@typescript-eslint.snap | 608 +++++++ 5 files changed, 2637 insertions(+), 244 deletions(-) create mode 100644 crates/oxc_linter/src/snapshots/eslint_no_shadow@typescript-eslint.snap diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs index c3938c8111530..04c05507471cc 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs @@ -3,12 +3,21 @@ mod options; #[cfg(test)] mod tests; -use oxc_ast::AstKind; +use std::path::Path; +use javascript_globals::GLOBALS; +use oxc_allocator::GetAddress; +use oxc_ast::{ + AstKind, + ast::{BindingPattern, Expression, FunctionType, TSModuleDeclarationName}, +}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; -use oxc_syntax::symbol::SymbolFlags; +use oxc_span::{GetSpan, Span}; +use oxc_syntax::{ + node::NodeId, + symbol::{SymbolFlags, SymbolId}, +}; use crate::{ context::LintContext, @@ -28,6 +37,12 @@ pub fn no_shadow_diagnostic(span: Span, name: &str, shadowed_span: Span) -> OxcD ]) } +pub fn no_shadow_global_diagnostic(span: Span, name: &str) -> OxcDiagnostic { + OxcDiagnostic::warn(format!("'{name}' is already a global variable.")) + .with_help(format!("Consider renaming '{name}' to avoid shadowing the global variable.")) + .with_label(span) +} + #[derive(Debug, Default, Clone)] pub struct NoShadow(Box); @@ -92,115 +107,130 @@ impl Rule for NoShadow { for symbol_id in scoping.symbol_ids() { let symbol_name = scoping.symbol_ident(symbol_id); + let symbol_name_str = symbol_name.as_str(); + let symbol_flags = scoping.symbol_flags(symbol_id); + let symbol_scope = scoping.symbol_scope_id(symbol_id); + let symbol_span = scoping.symbol_span(symbol_id); // Skip if in allow list - if self.allow.iter().any(|allowed| allowed.as_str() == symbol_name) { + if self.allow.iter().any(|allowed| allowed.as_str() == symbol_name_str) { continue; } - let symbol_scope = scoping.symbol_scope_id(symbol_id); - let symbol_flags = scoping.symbol_flags(symbol_id); - let symbol_span = scoping.symbol_span(symbol_id); + if symbol_name_str == "this" { + continue; + } - // Skip enum members - they don't shadow outer variables - if symbol_flags.contains(SymbolFlags::EnumMember) { + if Self::is_declare_in_definition_file(ctx, symbol_id) { continue; } - // Walk parent scopes looking for shadowed variables - for parent_scope in scoping.scope_ancestors(symbol_scope).skip(1) { - if let Some(shadowed_symbol_id) = scoping.get_binding(parent_scope, symbol_name) { - let shadowed_flags = scoping.symbol_flags(shadowed_symbol_id); - let shadowed_span = scoping.symbol_span(shadowed_symbol_id); + if Self::is_in_global_augmentation(ctx, symbol_id) { + continue; + } + + if let Some(shadowed_span) = + Self::function_expression_name_shadow_span(ctx, symbol_id, symbol_name_str) + { + ctx.diagnostic(no_shadow_diagnostic(symbol_span, symbol_name_str, shadowed_span)); + continue; + } + + let shadowed_symbol_id = scoping + .scope_ancestors(symbol_scope) + .skip(1) + .find_map(|scope_id| scoping.get_binding(scope_id, symbol_name)); + + if let Some(shadowed_symbol_id) = shadowed_symbol_id { + let shadowed_flags = scoping.symbol_flags(shadowed_symbol_id); + let shadowed_span = scoping.symbol_span(shadowed_symbol_id); - // Check if we should ignore this shadowing based on TypeScript rules - if self.should_ignore_shadow( + if Self::is_function_name_initializer_exception(ctx, symbol_id, shadowed_symbol_id) + || (self.ignore_on_initialization + && Self::is_init_pattern_node(ctx, symbol_id, shadowed_symbol_id)) + || (self.hoist != HoistOption::All + && self.is_in_tdz(ctx, symbol_id, shadowed_symbol_id)) + || self.should_ignore_shadow( ctx, symbol_id, symbol_flags, shadowed_symbol_id, shadowed_flags, - ) { - break; - } + ) + || Self::is_external_declaration_merging(ctx, symbol_id, shadowed_symbol_id) + { + continue; + } - // Check hoisting rules - if !self.check_hoisting(symbol_span, shadowed_span, shadowed_flags) { - continue; - } + ctx.diagnostic(no_shadow_diagnostic(symbol_span, symbol_name_str, shadowed_span)); + continue; + } - ctx.diagnostic(no_shadow_diagnostic( - symbol_span, - symbol_name.as_str(), - shadowed_span, - )); - break; + if self.builtin_globals && is_builtin_global_name(ctx, symbol_name_str) { + if self.should_ignore_global_shadow(ctx, symbol_id, symbol_flags) { + continue; } + + ctx.diagnostic(no_shadow_global_diagnostic(symbol_span, symbol_name_str)); } } } } impl NoShadow { + fn should_ignore_global_shadow( + &self, + ctx: &LintContext, + symbol_id: SymbolId, + symbol_flags: SymbolFlags, + ) -> bool { + (self.ignore_type_value_shadow && is_type_only(symbol_flags)) + || (self.ignore_function_type_parameter_name_value_shadow + && Self::is_function_type_parameter_name_value_shadow(ctx, symbol_id)) + || Self::is_generic_of_static_method_shadow(ctx, symbol_id) + } + /// Check if we should ignore this shadowing based on TypeScript-specific rules. fn should_ignore_shadow( &self, ctx: &LintContext, - symbol_id: oxc_syntax::symbol::SymbolId, + symbol_id: SymbolId, symbol_flags: SymbolFlags, - shadowed_symbol_id: oxc_syntax::symbol::SymbolId, + shadowed_symbol_id: SymbolId, shadowed_flags: SymbolFlags, ) -> bool { - // Check type vs value shadowing + // Ignore when one side is type-only and the other side is value-only. if self.ignore_type_value_shadow { - let symbol_is_type = is_type_only(symbol_flags); - let shadowed_is_type = is_type_only(shadowed_flags); + let symbol_is_value = symbol_flags.can_be_referenced_by_value(); + let shadowed_is_value = if shadowed_flags.is_type_import() { + false + } else { + shadowed_flags.can_be_referenced_by_value() + }; - // If one is a type and the other is a value, ignore - if symbol_is_type != shadowed_is_type { + if symbol_is_value != shadowed_is_value { return true; } } - // Check function type parameter shadowing value if self.ignore_function_type_parameter_name_value_shadow - && symbol_flags.contains(SymbolFlags::TypeParameter) + && Self::is_function_type_parameter_name_value_shadow(ctx, symbol_id) { - // Check if the type parameter is in a function context - let declaration_node_id = ctx.scoping().symbol_declaration(symbol_id); - let declaration_node = ctx.nodes().get_node(declaration_node_id); - - // Walk up to find if we're in a function - for ancestor in ctx.nodes().ancestor_ids(declaration_node.id()) { - let ancestor_node = ctx.nodes().get_node(ancestor); - if matches!( - ancestor_node.kind(), - AstKind::Function(_) - | AstKind::ArrowFunctionExpression(_) - | AstKind::TSMethodSignature(_) - | AstKind::TSCallSignatureDeclaration(_) - | AstKind::TSConstructSignatureDeclaration(_) - ) { - // This is a function type parameter, check if shadowed is a value - if !is_type_only(shadowed_flags) { - return true; - } - break; - } - } + return true; + } + + if Self::is_generic_of_static_method_shadow(ctx, symbol_id) { + return true; } - // Check if shadowing an import that's only used as a type + // Value imports that are only used as types are allowed to be shadowed by values. if shadowed_flags.contains(SymbolFlags::Import) { - // Check if all references to the import are type-only let references: Vec<_> = ctx.scoping().get_resolved_references(shadowed_symbol_id).collect(); let has_refs = !references.is_empty(); let all_type_refs = references.iter().all(|r| r.is_type()); if has_refs && all_type_refs && !is_type_only(symbol_flags) { - // The import is only used as a type, and we're declaring a value - // This is allowed in TypeScript return true; } } @@ -208,38 +238,537 @@ impl NoShadow { false } - /// Check if shadowing should be reported based on hoisting rules. - pub fn check_hoisting( + fn is_function_type_parameter_name_value_shadow(ctx: &LintContext, symbol_id: SymbolId) -> bool { + let declaration_id = ctx.scoping().symbol_declaration(symbol_id); + ctx.nodes().ancestor_kinds(declaration_id).any(|ancestor_kind| { + matches!( + ancestor_kind, + AstKind::TSCallSignatureDeclaration(_) + | AstKind::TSFunctionType(_) + | AstKind::TSMethodSignature(_) + | AstKind::TSConstructSignatureDeclaration(_) + | AstKind::TSConstructorType(_) + ) || matches!( + ancestor_kind, + AstKind::Function(func) + if matches!( + func.r#type, + FunctionType::TSDeclareFunction + | FunctionType::TSEmptyBodyFunctionExpression + ) + ) + }) + } + + fn is_generic_of_static_method_shadow(ctx: &LintContext, symbol_id: SymbolId) -> bool { + if !ctx.scoping().symbol_flags(symbol_id).contains(SymbolFlags::TypeParameter) { + return false; + } + + let declaration_id = ctx.scoping().symbol_declaration(symbol_id); + let mut type_parameter_decl_id = None; + + for ancestor_id in ctx.nodes().ancestor_ids(declaration_id) { + if matches!(ctx.nodes().kind(ancestor_id), AstKind::TSTypeParameterDeclaration(_)) { + type_parameter_decl_id = Some(ancestor_id); + break; + } + } + + let Some(type_parameter_decl_id) = type_parameter_decl_id else { + return false; + }; + + let function_like_id = ctx.nodes().parent_id(type_parameter_decl_id); + let method_like_id = ctx.nodes().parent_id(function_like_id); + + matches!(ctx.nodes().kind(method_like_id), AstKind::MethodDefinition(method) if method.r#static) + } + + fn is_in_tdz( &self, - symbol_span: Span, - shadowed_span: Span, - shadowed_flags: SymbolFlags, + ctx: &LintContext, + symbol_id: SymbolId, + shadowed_symbol_id: SymbolId, ) -> bool { + let inner_span = ctx.scoping().symbol_span(symbol_id); + let outer_span = ctx.scoping().symbol_span(shadowed_symbol_id); + + if inner_span.end >= outer_span.start { + return false; + } + match self.hoist { - HoistOption::All => true, - HoistOption::Functions => { - // Only report if the shadowed variable is a function or if the symbol - // comes after the shadowed declaration - shadowed_flags.contains(SymbolFlags::Function) - || symbol_span.start >= shadowed_span.start + HoistOption::All => false, + HoistOption::Never => true, + HoistOption::Functions => !Self::is_function_declaration(ctx, shadowed_symbol_id), + HoistOption::Types => !Self::is_hoisted_type_declaration(ctx, shadowed_symbol_id), + HoistOption::FunctionsAndTypes => { + !Self::is_function_declaration(ctx, shadowed_symbol_id) + && !Self::is_hoisted_type_declaration(ctx, shadowed_symbol_id) + } + } + } + + fn is_function_declaration(ctx: &LintContext, symbol_id: SymbolId) -> bool { + let declaration_id = + declaration_or_parent_id(ctx, ctx.scoping().symbol_declaration(symbol_id)); + matches!( + ctx.nodes().kind(declaration_id), + AstKind::Function(function) if function.is_function_declaration() + ) + } + + fn is_hoisted_type_declaration(ctx: &LintContext, symbol_id: SymbolId) -> bool { + let declaration_id = + declaration_or_parent_id(ctx, ctx.scoping().symbol_declaration(symbol_id)); + matches!( + ctx.nodes().kind(declaration_id), + AstKind::TSInterfaceDeclaration(_) | AstKind::TSTypeAliasDeclaration(_) + ) + } + + fn is_declare_in_definition_file(ctx: &LintContext, symbol_id: SymbolId) -> bool { + if !is_definition_file(ctx.file_path()) { + return false; + } + + let declaration_id = ctx.scoping().symbol_declaration(symbol_id); + for ancestor_kind in ctx.nodes().ancestor_kinds(declaration_id) { + match ancestor_kind { + AstKind::VariableDeclaration(declaration) if declaration.declare => return true, + AstKind::Function(function) + if function.is_ts_declare_function() || function.declare => + { + return true; + } + AstKind::Class(class) if class.declare => return true, + AstKind::TSEnumDeclaration(declaration) if declaration.declare => return true, + AstKind::TSModuleDeclaration(declaration) if declaration.declare => return true, + AstKind::TSInterfaceDeclaration(declaration) if declaration.declare => return true, + AstKind::TSTypeAliasDeclaration(declaration) if declaration.declare => return true, + AstKind::Program(_) => break, + _ => {} + } + } + + false + } + + fn is_in_global_augmentation(ctx: &LintContext, symbol_id: SymbolId) -> bool { + let declaration_id = ctx.scoping().symbol_declaration(symbol_id); + ctx.nodes().ancestor_kinds(declaration_id).any(|ancestor_kind| match ancestor_kind { + AstKind::TSGlobalDeclaration(_) => true, + AstKind::TSModuleDeclaration(module) => { + module.declare + && matches!( + &module.id, + TSModuleDeclarationName::Identifier(identifier) + if identifier.name.as_str() == "global" + ) + } + _ => false, + }) + } + + fn function_expression_name_shadow_span( + ctx: &LintContext, + symbol_id: SymbolId, + symbol_name: &str, + ) -> Option { + let symbol_declaration = ctx.scoping().symbol_declaration(symbol_id); + let symbol_scope = ctx.scoping().symbol_scope_id(symbol_id); + let scope_node_id = ctx.scoping().get_node_id(symbol_scope); + + if symbol_declaration == scope_node_id { + return None; + } + + match ctx.nodes().kind(scope_node_id) { + AstKind::Function(function) + if function.is_expression() + && function.id.as_ref().is_some_and(|id| id.name.as_str() == symbol_name) => + { + function.id.as_ref().map(GetSpan::span) + } + AstKind::Class(class) + if class.is_expression() + && class.id.as_ref().is_some_and(|id| id.name.as_str() == symbol_name) => + { + class.id.as_ref().map(GetSpan::span) + } + _ => None, + } + } + + fn is_external_declaration_merging( + ctx: &LintContext, + symbol_id: SymbolId, + shadowed_symbol_id: SymbolId, + ) -> bool { + let shadowed_flags = ctx.scoping().symbol_flags(shadowed_symbol_id); + if !shadowed_flags.is_type_import() { + return false; + } + + let shadowed_declaration_id = ctx.scoping().symbol_declaration(shadowed_symbol_id); + let import_source = ctx.nodes().ancestor_kinds(shadowed_declaration_id).find_map(|kind| { + if let AstKind::ImportDeclaration(declaration) = kind { + Some(declaration.source.value.as_str()) + } else { + None + } + }); + let Some(import_source) = import_source else { + return false; + }; + + let declaration_id = ctx.scoping().symbol_declaration(symbol_id); + let module_name = ctx.nodes().ancestor_kinds(declaration_id).find_map(|kind| { + if let AstKind::TSModuleDeclaration(module_decl) = kind { + match &module_decl.id { + TSModuleDeclarationName::Identifier(identifier) => { + Some(identifier.name.as_str()) + } + TSModuleDeclarationName::StringLiteral(string_literal) => { + Some(string_literal.value.as_str()) + } + } + } else { + None + } + }); + + module_name.is_some_and(|module_name| module_name == import_source) + } + + fn is_function_name_initializer_exception( + ctx: &LintContext, + inner_symbol_id: SymbolId, + outer_symbol_id: SymbolId, + ) -> bool { + let inner_expression_id = + declaration_or_parent_id(ctx, ctx.scoping().symbol_declaration(inner_symbol_id)); + if !matches!( + ctx.nodes().kind(inner_expression_id), + AstKind::Function(function) if function.is_expression() + ) && !matches!( + ctx.nodes().kind(inner_expression_id), + AstKind::Class(class) if class.is_expression() + ) { + return false; + } + + let outer_declaration_id = ctx.scoping().symbol_declaration(outer_symbol_id); + let outer_symbol_span = ctx.scoping().symbol_span(outer_symbol_id); + let Some(initializer) = + Self::find_initializer_expression(ctx, outer_declaration_id, outer_symbol_span) + else { + return false; + }; + + let expression_span = ctx.nodes().kind(inner_expression_id).span(); + let initializer_span = initializer.span(); + if initializer_span.start > expression_span.start + || expression_span.end > initializer_span.end + { + return false; + } + + let unwrapped_expression_id = Self::unwrap_expression(ctx, inner_expression_id); + initializer.address() == ctx.nodes().kind(unwrapped_expression_id).address() + } + + fn is_init_pattern_node(ctx: &LintContext, symbol_id: SymbolId, shadowed_symbol_id: SymbolId) -> bool { + let scoping = ctx.scoping(); + + let variable_scope_id = scoping.symbol_scope_id(symbol_id); + let variable_scope_node_id = scoping.get_node_id(variable_scope_id); + let variable_scope_node_kind = ctx.nodes().kind(variable_scope_node_id); + + if !matches!( + variable_scope_node_kind, + AstKind::Function(function) if function.is_expression() + ) && !matches!(variable_scope_node_kind, AstKind::ArrowFunctionExpression(_)) + { + return false; + } + + let Some(outer_scope_id) = scoping.scope_parent_id(variable_scope_id) else { + return false; + }; + + if outer_scope_id != scoping.symbol_scope_id(shadowed_symbol_id) { + return false; + } + + let call_expression_end = + ctx.nodes().ancestor_ids(variable_scope_node_id).find_map(|ancestor_id| { + match ctx.nodes().kind(ancestor_id) { + AstKind::CallExpression(call_expression) => Some(call_expression.span.end), + _ => None, + } + }); + let Some(call_expression_end) = call_expression_end else { + return false; + }; + + let outer_declaration_id = scoping.symbol_declaration(shadowed_symbol_id); + let shadowed_symbol_span = scoping.symbol_span(shadowed_symbol_id); + for node_id in std::iter::once(outer_declaration_id) + .chain(ctx.nodes().ancestor_ids(outer_declaration_id)) + { + match ctx.nodes().kind(node_id) { + AstKind::VariableDeclarator(declarator) => { + if declarator + .init + .as_ref() + .is_some_and(|init| is_in_range(init.span(), call_expression_end)) + { + return true; + } + + if pattern_initializer_contains_location( + &declarator.id, + shadowed_symbol_span, + call_expression_end, + ) { + return true; + } + + let variable_declaration_id = ctx.nodes().parent_id(node_id); + match ctx.nodes().parent_kind(variable_declaration_id) { + AstKind::ForInStatement(statement) + if is_in_range(statement.right.span(), call_expression_end) => + { + return true; + } + AstKind::ForOfStatement(statement) + if is_in_range(statement.right.span(), call_expression_end) => + { + return true; + } + _ => {} + } + + break; + } + AstKind::FormalParameter(parameter) => { + if binding_pattern_contains_symbol(¶meter.pattern, shadowed_symbol_span) + && parameter + .initializer + .as_ref() + .is_some_and(|init| is_in_range(init.span(), call_expression_end)) + { + return true; + } + } + AstKind::AssignmentPattern(pattern) => { + if binding_pattern_contains_symbol(&pattern.left, shadowed_symbol_span) + && is_in_range(pattern.right.span(), call_expression_end) + { + return true; + } + } + kind if is_initializer_sentinel(kind) => break, + _ => {} + } + } + + false + } + + fn find_initializer_expression<'a>( + ctx: &LintContext<'a>, + declaration_id: NodeId, + symbol_span: Span, + ) -> Option<&'a Expression<'a>> { + for node_id in + std::iter::once(declaration_id).chain(ctx.nodes().ancestor_ids(declaration_id)) + { + match ctx.nodes().kind(node_id) { + AstKind::FormalParameter(parameter) => { + if binding_pattern_contains_symbol(¶meter.pattern, symbol_span) + && let Some(initializer) = parameter.initializer.as_ref() + { + return Some(initializer); + } + } + AstKind::AssignmentPattern(pattern) => { + if binding_pattern_contains_symbol(&pattern.left, symbol_span) { + return Some(&pattern.right); + } + } + AstKind::VariableDeclarator(declarator) => { + if let Some(initializer) = + find_pattern_initializer_for_symbol(&declarator.id, symbol_span) + { + return Some(initializer); + } + return declarator.init.as_ref(); + } + kind if is_initializer_sentinel(kind) => break, + _ => {} } - HoistOption::Never => { - // Only report if the symbol comes after the shadowed declaration - symbol_span.start >= shadowed_span.start + } + None + } + + fn unwrap_expression(ctx: &LintContext, expression_id: NodeId) -> NodeId { + let mut current_id = expression_id; + + loop { + let parent_id = ctx.nodes().parent_id(current_id); + match ctx.nodes().kind(parent_id) { + AstKind::ParenthesizedExpression(_) | AstKind::LogicalExpression(_) => { + current_id = parent_id; + } + AstKind::ConditionalExpression(conditional_expression) => { + if conditional_expression.test.address() == ctx.nodes().kind(current_id).address() + { + break; + } + current_id = parent_id; + } + _ => break, } } + + current_id } } /// Check if the symbol is a type-only declaration (not a value). fn is_type_only(flags: SymbolFlags) -> bool { - flags.intersects(SymbolFlags::TypeAlias | SymbolFlags::Interface | SymbolFlags::TypeParameter) - && !flags.intersects( - SymbolFlags::FunctionScopedVariable - | SymbolFlags::BlockScopedVariable - | SymbolFlags::Function - | SymbolFlags::Class - | SymbolFlags::Enum - | SymbolFlags::ConstEnum, - ) + flags.can_be_referenced_by_type() && !flags.can_be_referenced_by_value() +} + +fn is_builtin_global_name(ctx: &LintContext, name: &str) -> bool { + GLOBALS.values().any(|globals| globals.contains_key(name)) || ctx.globals().is_enabled(name) +} + +fn is_definition_file(path: &Path) -> bool { + let path = path.to_string_lossy(); + path.ends_with(".d.ts") || path.ends_with(".d.cts") || path.ends_with(".d.mts") +} + +fn is_in_range(span: Span, location: u32) -> bool { + span.start <= location && location <= span.end +} + +fn is_initializer_sentinel(kind: AstKind) -> bool { + matches!( + kind, + AstKind::Function(_) + | AstKind::Class(_) + | AstKind::ArrowFunctionExpression(_) + | AstKind::CatchClause(_) + | AstKind::ImportDeclaration(_) + | AstKind::ExportNamedDeclaration(_) + ) +} + +fn declaration_or_parent_id(ctx: &LintContext, declaration_id: NodeId) -> NodeId { + if matches!(ctx.nodes().kind(declaration_id), AstKind::BindingIdentifier(_)) { + ctx.nodes().parent_id(declaration_id) + } else { + declaration_id + } +} + +fn binding_pattern_contains_symbol(pattern: &BindingPattern, symbol_span: Span) -> bool { + match pattern { + BindingPattern::BindingIdentifier(identifier) => identifier.span == symbol_span, + BindingPattern::AssignmentPattern(pattern) => { + binding_pattern_contains_symbol(&pattern.left, symbol_span) + } + BindingPattern::ObjectPattern(pattern) => { + pattern + .properties + .iter() + .any(|property| binding_pattern_contains_symbol(&property.value, symbol_span)) + || pattern.rest.as_ref().is_some_and(|rest| { + binding_pattern_contains_symbol(&rest.argument, symbol_span) + }) + } + BindingPattern::ArrayPattern(pattern) => { + pattern + .elements + .iter() + .flatten() + .any(|element| binding_pattern_contains_symbol(element, symbol_span)) + || pattern.rest.as_ref().is_some_and(|rest| { + binding_pattern_contains_symbol(&rest.argument, symbol_span) + }) + } + } +} + +fn pattern_initializer_contains_location( + pattern: &BindingPattern, + symbol_span: Span, + location: u32, +) -> bool { + match pattern { + BindingPattern::BindingIdentifier(_) => false, + BindingPattern::AssignmentPattern(pattern) => { + (binding_pattern_contains_symbol(&pattern.left, symbol_span) + && is_in_range(pattern.right.span(), location)) + || pattern_initializer_contains_location(&pattern.left, symbol_span, location) + } + BindingPattern::ObjectPattern(pattern) => { + pattern.properties.iter().any(|property| { + pattern_initializer_contains_location(&property.value, symbol_span, location) + }) || pattern.rest.as_ref().is_some_and(|rest| { + pattern_initializer_contains_location(&rest.argument, symbol_span, location) + }) + } + BindingPattern::ArrayPattern(pattern) => { + pattern.elements.iter().flatten().any(|element| { + pattern_initializer_contains_location(element, symbol_span, location) + }) || pattern.rest.as_ref().is_some_and(|rest| { + pattern_initializer_contains_location(&rest.argument, symbol_span, location) + }) + } + } +} + +fn find_pattern_initializer_for_symbol<'a>( + pattern: &'a BindingPattern<'a>, + symbol_span: Span, +) -> Option<&'a Expression<'a>> { + match pattern { + BindingPattern::BindingIdentifier(_) => None, + BindingPattern::AssignmentPattern(pattern) => { + if binding_pattern_contains_symbol(&pattern.left, symbol_span) { + return Some(&pattern.right); + } + find_pattern_initializer_for_symbol(&pattern.left, symbol_span) + } + BindingPattern::ObjectPattern(pattern) => { + for property in &pattern.properties { + if let Some(initializer) = + find_pattern_initializer_for_symbol(&property.value, symbol_span) + { + return Some(initializer); + } + } + pattern + .rest + .as_ref() + .and_then(|rest| find_pattern_initializer_for_symbol(&rest.argument, symbol_span)) + } + BindingPattern::ArrayPattern(pattern) => { + for element in pattern.elements.iter().flatten() { + if let Some(initializer) = find_pattern_initializer_for_symbol(element, symbol_span) + { + return Some(initializer); + } + } + pattern + .rest + .as_ref() + .and_then(|rest| find_pattern_initializer_for_symbol(&rest.argument, symbol_span)) + } + } } diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/options.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/options.rs index abfe91f74d5b5..95517c4b1fb7a 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow/options.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/options.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; /// Controls how hoisting is handled when checking for shadowing. #[derive(Debug, Clone, Default, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] pub enum HoistOption { /// Report shadowing even before the outer variable is declared (due to hoisting). All, diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs index 12bb4e738d7de..993a6b3fe906c 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs @@ -258,8 +258,6 @@ fn test_eslint() { ("function foo(a) { } var a;", None, None, None), // { "ecmaVersion": 6 }, ("function foo() { var Object = 0; }", None, None, None), ("function foo() { var top = 0; }", None, None, None), // { "globals": globals.browser }, - ("var Object = 0;", Some(serde_json::json!([{ "builtinGlobals": true }])), None, None), - ("var top = 0;", Some(serde_json::json!([{ "builtinGlobals": true }])), None, None), // { "globals": globals.browser }, ( "function foo(cb) { (function (cb) { cb(42); })(cb); }", Some(serde_json::json!([{ "allow": ["cb"] }])), @@ -1808,7 +1806,7 @@ fn test_eslint() { Some( serde_json::json!([ { "builtinGlobals": true, "ignoreTypeValueShadow": false, }, ]), ), - None, + Some(serde_json::json!({ "globals": { "Foo": "writable" } })), None, ), // { "globals": { "Foo": "writable", }, }, ( @@ -1827,7 +1825,7 @@ fn test_eslint() { Some( serde_json::json!([ { "builtinGlobals": true, "ignoreFunctionTypeParameterNameValueShadow": false, }, ]), ), - None, + Some(serde_json::json!({ "globals": { "Foo": "writable" } })), None, ), // { "globals": { "Foo": "writable", }, }, ( @@ -2234,7 +2232,7 @@ fn test_eslint() { Some( serde_json::json!([ { "builtinGlobals": true, "ignoreTypeValueShadow": false, }, ]), ), - None, + Some(serde_json::json!({ "globals": { "args": "writable" } })), None, ), // { "globals": { "args": "writable", }, }, ( @@ -2242,7 +2240,7 @@ fn test_eslint() { declare const has = (environment: 'dev' | 'prod' | 'test') => boolean; ", Some(serde_json::json!([{ "builtinGlobals": true }])), - None, + Some(serde_json::json!({ "globals": { "has": false } })), None, ), // { "globals": { "has": false, }, }, ( @@ -3223,7 +3221,7 @@ fn test_typescript_eslint() { Some( serde_json::json!([ { "builtinGlobals": true, "ignoreTypeValueShadow": false, }, ]), ), - None, + Some(serde_json::json!({ "globals": { "Foo": "writable" } })), None, ), // { "globals": { "Foo": "writable", }, }, ( @@ -3242,7 +3240,7 @@ fn test_typescript_eslint() { Some( serde_json::json!([ { "builtinGlobals": true, "ignoreFunctionTypeParameterNameValueShadow": false, }, ]), ), - None, + Some(serde_json::json!({ "globals": { "Foo": "writable" } })), None, ), // { "globals": { "Foo": "writable", }, }, ( @@ -3603,7 +3601,7 @@ fn test_typescript_eslint() { Some( serde_json::json!([ { "builtinGlobals": true, "ignoreTypeValueShadow": false, }, ]), ), - None, + Some(serde_json::json!({ "globals": { "args": "writable" } })), None, ), // { "globals": { "args": "writable", }, }, ( @@ -3611,7 +3609,7 @@ fn test_typescript_eslint() { declare const has = (environment: 'dev' | 'prod' | 'test') => boolean; ", Some(serde_json::json!([{ "builtinGlobals": true }])), - None, + Some(serde_json::json!({ "globals": { "has": false } })), None, ), // { "globals": { "has": false, }, }, ( diff --git a/crates/oxc_linter/src/snapshots/eslint_no_shadow.snap b/crates/oxc_linter/src/snapshots/eslint_no_shadow.snap index 5501531160c26..0928a007a841a 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_shadow.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_shadow.snap @@ -3,98 +3,595 @@ source: crates/oxc_linter/src/tester.rs --- ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:5] - 1 │ var x = 1; function foo() { var x = 2; } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here + ╭─[no_shadow.tsx:1:12] + 1 │ function a(x) { var b = function c() { var x = 'foo'; }; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:7] - 1 │ const x = 1; { const x = 2; } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here + ╭─[no_shadow.tsx:1:10] + 1 │ var a = (x) => { var b = () => { var x = 'foo'; }; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:12] + 1 │ function a(x) { var b = function () { var x = 'foo'; }; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. ╭─[no_shadow.tsx:1:5] - 1 │ var x = 1; function foo(x) { } - · ┬ ┬ - · │ ╰── 'x' is declared here + 1 │ var x = 1; function a(x) { return ++x; } + · ┬ ┬ + · │ ╰── 'x' is declared here · ╰── shadowed declaration is here ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ var a=3; function b() { var a=10; } + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ var a=3; function b() { var a=10; }; setTimeout(function() { b(); }, 0); + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ var a=3; function b() { var a=10; var b=0; }; setTimeout(function() { b(); }, 0); + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'b' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:19] + 1 │ var a=3; function b() { var a=10; var b=0; }; setTimeout(function() { b(); }, 0); + · ┬ ┬ + · │ ╰── 'b' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'b' to avoid shadowing the variable from the outer scope. + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:22] - 1 │ function foo() { var x = 1; function bar() { var x = 2; } } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here + ╭─[no_shadow.tsx:1:5] + 1 │ var x = 1; { let x = 2; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ let x = 1; { const x = 2; } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ { let a; } function a() {} + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:9] + 1 │ { const a = 0; } function a() {} + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:22] + 1 │ function foo() { let a; } function a() {} + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:22] + 1 │ function foo() { var a; } function a() {} + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:14] + 1 │ function foo(a) { } function a() {} + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. ╭─[no_shadow.tsx:1:7] - 1 │ const x = 1; const foo = () => { const x = 2; }; - · ┬ ┬ - · │ ╰── 'x' is declared here + 1 │ { let a; } let a; + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ { let a; } var a; + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ { let a; } function a() {} + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:9] + 1 │ { const a = 0; } const a = 1; + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:9] + 1 │ { const a = 0; } var a; + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:9] + 1 │ { const a = 0; } function a() {} + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:22] + 1 │ function foo() { let a; } let a; + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:22] + 1 │ function foo() { let a; } var a; + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:22] + 1 │ function foo() { let a; } function a() {} + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:22] + 1 │ function foo() { var a; } let a; + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:22] + 1 │ function foo() { var a; } var a; + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:22] + 1 │ function foo() { var a; } function a() {} + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:14] + 1 │ function foo(a) { } let a; + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:14] + 1 │ function foo(a) { } var a; + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:14] + 1 │ function foo(a) { } function a() {} + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:11] + 1 │ (function a() { function a(){} })() + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:11] + 1 │ (function a() { class a{} })() + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:11] + 1 │ (function a() { (function a(){}); })() + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:11] + 1 │ (function a() { (class a{}); })() + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:19] + 1 │ (function() { var a = function(a) {}; })() + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:19] + 1 │ (function() { var a = function() { function a() {} }; })() + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:19] + 1 │ (function() { var a = function() { class a{} }; })() + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:19] + 1 │ (function() { var a = function() { (function a() {}); }; })() + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:19] + 1 │ (function() { var a = function() { (class a{}); }; })() + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:19] + 1 │ (function() { var a = class { constructor() { class a {} } }; })() + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ class A { constructor() { var A; } } + · ┬ ┬ + · │ ╰── 'A' is declared here · ╰── shadowed declaration is here ╰──── - help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. - ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:11] + 1 │ (function a() { function a(){ function a(){} } })() + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:26] + 1 │ (function a() { function a(){ function a(){} } })() + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Object' is already a global variable. + ╭─[no_shadow.tsx:1:22] + 1 │ function foo() { var Object = 0; } + · ────── + ╰──── + help: Consider renaming 'Object' to avoid shadowing the global variable. + + ⚠ eslint(no-shadow): 'top' is already a global variable. + ╭─[no_shadow.tsx:1:22] + 1 │ function foo() { var top = 0; } + · ─── + ╰──── + help: Consider renaming 'top' to avoid shadowing the global variable. + + ⚠ eslint(no-shadow): 'Object' is already a global variable. + ╭─[no_shadow.tsx:1:5] + 1 │ var Object = 0; + · ────── + ╰──── + help: Consider renaming 'Object' to avoid shadowing the global variable. + + ⚠ eslint(no-shadow): 'top' is already a global variable. + ╭─[no_shadow.tsx:1:5] + 1 │ var top = 0; + · ─── + ╰──── + help: Consider renaming 'top' to avoid shadowing the global variable. + + ⚠ eslint(no-shadow): 'Object' is already a global variable. + ╭─[no_shadow.tsx:1:5] + 1 │ var Object = 0; + · ────── + ╰──── + help: Consider renaming 'Object' to avoid shadowing the global variable. + + ⚠ eslint(no-shadow): 'top' is already a global variable. + ╭─[no_shadow.tsx:1:5] + 1 │ var top = 0; + · ─── + ╰──── + help: Consider renaming 'top' to avoid shadowing the global variable. + + ⚠ eslint(no-shadow): 'cb' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:14] + 1 │ function foo(cb) { (function (cb) { cb(42); })(cb); } + · ─┬ ─┬ + · │ ╰── 'cb' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'cb' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:24] + 1 │ class C { static { let a; { let a; } } } + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'C' is already declared in the upper scope. ╭─[no_shadow.tsx:1:7] - 1 │ const x = 1; class Foo { method() { const x = 2; } } - · ┬ ┬ - · │ ╰── 'x' is declared here + 1 │ class C { static { var C; } } + · ┬ ┬ + · │ ╰── 'C' is declared here · ╰── shadowed declaration is here ╰──── - help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + help: Consider renaming 'C' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'C' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ class C { static { let C; } } + · ┬ ┬ + · │ ╰── 'C' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'C' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ var a; class C { static { var a; } } + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:24] + 1 │ class C { static { var a; } } var a; + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:24] + 1 │ class C { static { let a; } } let a; + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:24] + 1 │ class C { static { var a; } } let a; + · ┬ ┬ + · │ ╰── shadowed declaration is here + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:24] + 1 │ class C { static { var a; class D { static { var a; } } } } + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:24] + 1 │ class C { static { let a; class D { static { let a; } } } } + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. ╭─[no_shadow.tsx:1:5] - 1 │ let x = 1; { let x = 2; } - · ┬ ┬ - · │ ╰── 'x' is declared here + 1 │ let x = foo((x,y) => {}); + · ┬ ┬ + · │ ╰── 'x' is declared here · ╰── shadowed declaration is here + 2 │ let y; ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. - ⚠ eslint(no-shadow): 'e' is already declared in the upper scope. + ⚠ eslint(no-shadow): 'y' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:16] + 1 │ let x = foo((x,y) => {}); + · ┬ + · ╰── 'y' is declared here + 2 │ let y; + · ┬ + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'y' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. ╭─[no_shadow.tsx:1:7] - 1 │ const e = 1; try { } catch (e) { } - · ┬ ┬ - · │ ╰── 'e' is declared here + 1 │ const a = fn(()=>{ class C { fn () { const a = 42; return a } } return new C() }) + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:10] + 1 │ function a() {} + · ┬ + · ╰── shadowed declaration is here + 2 │ foo(a => {}); + · ┬ + · ╰── 'a' is declared here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const a = fn(()=>{ function C() { this.fn=function() { const a = 42; return a } } return new C() }); + · ┬ ┬ + · │ ╰── 'a' is declared here · ╰── shadowed declaration is here ╰──── - help: Consider renaming 'e' to avoid shadowing the variable from the outer scope. + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. - ⚠ eslint(no-shadow): 'i' is already declared in the upper scope. + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. ╭─[no_shadow.tsx:1:7] - 1 │ const i = 1; for (let i = 0; i < 10; i++) { } - · ┬ ┬ - · │ ╰── 'i' is declared here + 1 │ const x = foo(() => { const bar = () => { return x => {}; }; return bar; }); + · ┬ ┬ + · │ ╰── 'x' is declared here · ╰── shadowed declaration is here ╰──── - help: Consider renaming 'i' to avoid shadowing the variable from the outer scope. + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. ╭─[no_shadow.tsx:1:7] - 1 │ const x = 1; { const { x } = { x: 2 }; } - · ┬ ┬ - · │ ╰── 'x' is declared here + 1 │ const x = foo(() => { return { bar(x) {} }; }); + · ┬ ┬ + · │ ╰── 'x' is declared here · ╰── shadowed declaration is here ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. ╭─[no_shadow.tsx:1:7] - 1 │ const x = 1; { const [x] = [2]; } + 1 │ const x = () => { foo(x => x); } · ┬ ┬ · │ ╰── 'x' is declared here · ╰── shadowed declaration is here @@ -102,159 +599,920 @@ source: crates/oxc_linter/src/tester.rs help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:5] - 1 │ let x = 1; { { let x = 3; } let x = 2; } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here + ╭─[no_shadow.tsx:1:25] + 1 │ const foo = () => { let x; bar(x => x); } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:19] + 1 │ foo(() => { const x = x => x; }); + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:14] + 1 │ const foo = (x) => { bar(x => {}) } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. ╭─[no_shadow.tsx:1:5] - 1 │ let x = 1; { { let x = 3; } let x = 2; } - · ┬ ┬ - · │ ╰── 'x' is declared here + 1 │ let x = ((x,y) => {})(); + · ┬ ┬ + · │ ╰── 'x' is declared here · ╰── shadowed declaration is here + 2 │ let y; ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. - ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:6] - 1 │ type Foo = string; { type Foo = number; } - · ─┬─ ─┬─ - · │ ╰── 'Foo' is declared here - · ╰── shadowed declaration is here + ⚠ eslint(no-shadow): 'y' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:13] + 1 │ let x = ((x,y) => {})(); + · ┬ + · ╰── 'y' is declared here + 2 │ let y; + · ┬ + · ╰── shadowed declaration is here ╰──── - help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + help: Consider renaming 'y' to avoid shadowing the variable from the outer scope. - ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:11] - 1 │ interface Foo { x: number } { interface Foo { y: string } } - · ─┬─ ─┬─ - · │ ╰── 'Foo' is declared here - · ╰── shadowed declaration is here + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const a = (()=>{ class C { fn () { const a = 42; return a } } return new C() })() + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here ╰──── - help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:22] - 1 │ function f() { { let x = 1; } let x = 2; } - · ┬ ┬ - · │ ╰── shadowed declaration is here - · ╰── 'x' is declared here + ╭─[no_shadow.tsx:1:7] + 1 │ const x = () => { (x => x)(); } + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. - ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:22] - 1 │ function f() { { let x = 1; } function x() {} } - · ┬ ┬ - · │ ╰── shadowed declaration is here - · ╰── 'x' is declared here + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:29] + 1 │ let x = false; export const a = wrap(function a() { if (!x) { x = true; a(); } }); + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here ╰──── - help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const a = wrap(function a() {}); + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const a = foo || wrap(function a() {}); + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:9] + 1 │ const { a = wrap(function a() {}) } = obj; + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:9] + 1 │ const { a = foo || wrap(function a() {}) } = obj; + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:9] + 1 │ const { a = foo, b = function a() {} } = {} + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:9] + 1 │ const { A = Foo, B = class A {} } = {} + · ┬ ┬ + · │ ╰── 'A' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:14] + 1 │ function foo(a = wrap(function a() {})) {} + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:14] + 1 │ function foo(a = foo || wrap(function a() {})) {} + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const A = wrap(class A {}); + · ┬ ┬ + · │ ╰── 'A' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:7] + 1 │ const A = foo || wrap(class A {}); + · ┬ ┬ + · │ ╰── 'A' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:9] + 1 │ const { A = wrap(class A {}) } = obj; + · ┬ ┬ + · │ ╰── 'A' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:9] + 1 │ const { A = foo || wrap(class A {}) } = obj; + · ┬ ┬ + · │ ╰── 'A' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:14] + 1 │ function foo(A = wrap(class A {})) {} + · ┬ ┬ + · │ ╰── 'A' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:14] + 1 │ function foo(A = foo || wrap(class A {})) {} + · ┬ ┬ + · │ ╰── 'A' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ var a = function a() {} ? foo : bar + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ var A = class A {} ? foo : bar + · ┬ ┬ + · │ ╰── 'A' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Array' is already a global variable. + ╭─[no_shadow.tsx:1:11] + 1 │ (function Array() {}) + · ───── + ╰──── + help: Consider renaming 'Array' to avoid shadowing the global variable. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ let a; { let b = (function a() {}) } + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'a' is already declared in the upper scope. + ╭─[no_shadow.tsx:1:5] + 1 │ let a = foo; { let b = (function a() {}) } + · ┬ ┬ + · │ ╰── 'a' is declared here + · ╰── shadowed declaration is here + ╰──── + help: Consider renaming 'a' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'T' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:20] + 1 │ + 2 │ type T = 1; + · ┬ + · ╰── shadowed declaration is here + 3 │ { + 4 │ type T = 2; + · ┬ + · ╰── 'T' is declared here + 5 │ } + ╰──── + help: Consider renaming 'T' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'T' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:20] + 1 │ + 2 │ type T = 1; + · ┬ + · ╰── shadowed declaration is here + 3 │ function foo(arg: T) {} + · ┬ + · ╰── 'T' is declared here + 4 │ + ╰──── + help: Consider renaming 'T' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'T' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:28] + 1 │ + 2 │ function foo() { + · ┬ + · ╰── shadowed declaration is here + 3 │ return function () {}; + · ┬ + · ╰── 'T' is declared here + 4 │ } + ╰──── + help: Consider renaming 'T' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'T' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:20] + 1 │ + 2 │ type T = string; + · ┬ + · ╰── shadowed declaration is here + 3 │ function foo void>(arg: T) {} + · ┬ + · ╰── 'T' is declared here + 4 │ + ╰──── + help: Consider renaming 'T' to avoid shadowing the variable from the outer scope. ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:22] - 1 │ function f() { { let x = 1; } var x = 2; } - · ┬ ┬ - · │ ╰── shadowed declaration is here + ╭─[no_shadow.tsx:2:21] + 1 │ + 2 │ const x = 1; + · ┬ + · ╰── shadowed declaration is here + 3 │ { + 4 │ type x = string; + · ┬ · ╰── 'x' is declared here + 5 │ } ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + ⚠ eslint(no-shadow): 'Foo' is already a global variable. + ╭─[no_shadow.tsx:2:20] + 1 │ + 2 │ type Foo = 1; + · ─── + 3 │ + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the global variable. + + ⚠ eslint(no-shadow): 'test' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:21] + 1 │ + 2 │ const test = 1; + · ──┬─ + · ╰── shadowed declaration is here + 3 │ type Fn = (test: string) => typeof test; + · ──┬─ + · ╰── 'test' is declared here + 4 │ + ╰──── + help: Consider renaming 'test' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Foo' is already a global variable. + ╭─[no_shadow.tsx:2:26] + 1 │ + 2 │ type Fn = (Foo: string) => typeof Foo; + · ─── + 3 │ + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the global variable. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:21] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ interface Test { + 5 │ (arg: string): typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 6 │ } + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:21] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ interface Test { + 5 │ p1(arg: string): typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 6 │ } + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:21] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare function test(arg: string): typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 5 │ + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:21] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare const test: (arg: string) => typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 5 │ + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:21] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare class Test { + 5 │ p1(arg: string): typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 6 │ } + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:21] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare const Test: { + 5 │ new (arg: string): typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 6 │ }; + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:21] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ type Bar = new (arg: number) => typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 5 │ + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:21] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare namespace Lib { + 5 │ function test(arg: string): typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 6 │ } + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:29] + 1 │ + 2 │ import type { foo } from './foo'; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ function doThing(foo: number) {} + · ─┬─ + · ╰── 'foo' is declared here + 4 │ + ╰──── + help: Consider renaming 'foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:29] + 1 │ + 2 │ import { type foo } from './foo'; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ function doThing(foo: number) {} + · ─┬─ + · ╰── 'foo' is declared here + 4 │ + ╰──── + help: Consider renaming 'foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:24] + 1 │ + 2 │ import { foo } from './foo'; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ function doThing(foo: number, bar: number) {} + · ─┬─ + · ╰── 'foo' is declared here + 4 │ + ╰──── + help: Consider renaming 'foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:25] + 1 │ + 2 │ interface Foo {} + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare module 'bar' { + 5 │ export interface Foo { + · ─┬─ + · ╰── 'Foo' is declared here + 6 │ x: string; + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:29] + 1 │ + 2 │ import type { Foo } from 'bar'; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare module 'baz' { + 5 │ export interface Foo { + · ─┬─ + · ╰── 'Foo' is declared here + 6 │ x: string; + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:29] + 1 │ + 2 │ import { type Foo } from 'bar'; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare module 'baz' { + 5 │ export interface Foo { + · ─┬─ + · ╰── 'Foo' is declared here + 6 │ x: string; + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:20] - 1 │ function f() { let x = 2; { let x = 1; } } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here + ╭─[no_shadow.tsx:2:19] + 1 │ + 2 │ let x = foo((x, y) => {}); + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + 3 │ let y; ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. ⚠ eslint(no-shadow): 'y' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:5] - 1 │ let y = 1; function g(){ let y = 2; } - · ┬ ┬ - · │ ╰── 'y' is declared here - · ╰── shadowed declaration is here + ╭─[no_shadow.tsx:2:31] + 1 │ + 2 │ let x = foo((x, y) => {}); + · ┬ + · ╰── 'y' is declared here + 3 │ let y; + · ┬ + · ╰── shadowed declaration is here + 4 │ ╰──── help: Consider renaming 'y' to avoid shadowing the variable from the outer scope. ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:5] - 1 │ let x = 1; function f(){ let x = 2; } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here + ╭─[no_shadow.tsx:2:19] + 1 │ + 2 │ let x = foo((x, y) => {}); + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + 3 │ let y; ╰──── help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:24] + 1 │ + 2 │ type Foo = 1; + · ┬ + · ╰── 'A' is declared here + 3 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:29] + 1 │ + 2 │ interface Foo {} + · ┬ + · ╰── 'A' is declared here + 3 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:29] + 1 │ + 2 │ interface Foo {} + · ┬ + · ╰── 'A' is declared here + 3 │ interface A {} + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:24] + 1 │ + 2 │ type Foo = 1; + · ┬ + · ╰── 'A' is declared here + 3 │ interface A {} + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:3:19] + 2 │ { + 3 │ type A = 1; + · ┬ + · ╰── 'A' is declared here + 4 │ } + 5 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 6 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:3:24] + 2 │ { + 3 │ interface A {} + · ┬ + · ╰── 'A' is declared here + 4 │ } + 5 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 6 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:24] + 1 │ + 2 │ type Foo = 1; + · ┬ + · ╰── 'A' is declared here + 3 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:29] + 1 │ + 2 │ interface Foo {} + · ┬ + · ╰── 'A' is declared here + 3 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:29] + 1 │ + 2 │ interface Foo {} + · ┬ + · ╰── 'A' is declared here + 3 │ interface A {} + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:24] + 1 │ + 2 │ type Foo = 1; + · ┬ + · ╰── 'A' is declared here + 3 │ interface A {} + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:3:19] + 2 │ { + 3 │ type A = 1; + · ┬ + · ╰── 'A' is declared here + 4 │ } + 5 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 6 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:3:24] + 2 │ { + 3 │ interface A {} + · ┬ + · ╰── 'A' is declared here + 4 │ } + 5 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 6 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:24] + 1 │ + 2 │ type Foo = 1; + · ┬ + · ╰── 'A' is declared here + 3 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:3:21] + 2 │ if (true) { + 3 │ const foo = 6; + · ─┬─ + · ╰── 'foo' is declared here + 4 │ } + 5 │ + 6 │ function foo() { } + · ─┬─ + · ╰── shadowed declaration is here + 7 │ + ╰──── + help: Consider renaming 'foo' to avoid shadowing the variable from the outer scope. + ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:6] - 1 │ type Foo = string; { const Foo = 'bar'; } - · ─┬─ ─┬─ - · │ ╰── 'Foo' is declared here - · ╰── shadowed declaration is here + ╭─[no_shadow.tsx:3:23] + 2 │ // types + 3 │ type Bar = 1; + · ─┬─ + · ╰── 'Foo' is declared here + 4 │ type Foo = 1; + · ─┬─ + · ╰── shadowed declaration is here + 5 │ ╰──── help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + ⚠ eslint(no-shadow): 'b' is already declared in the upper scope. + ╭─[no_shadow.tsx:8:21] + 7 │ if (true) { + 8 │ const b = 6; + · ┬ + · ╰── 'b' is declared here + 9 │ } + 10 │ + 11 │ function b() { } + · ┬ + · ╰── shadowed declaration is here + 12 │ + ╰──── + help: Consider renaming 'b' to avoid shadowing the variable from the outer scope. + ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:11] - 1 │ interface Foo { x: number }; { const Foo = { x: 1 } }; - · ─┬─ ─┬─ - · │ ╰── 'Foo' is declared here - · ╰── shadowed declaration is here + ╭─[no_shadow.tsx:3:23] + 2 │ // types + 3 │ type Bar = 1; + · ─┬─ + · ╰── 'Foo' is declared here + 4 │ type Foo = 1; + · ─┬─ + · ╰── shadowed declaration is here + 5 │ ╰──── help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. - ⚠ eslint(no-shadow): 'T' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:7] - 1 │ const T = 1; function foo() { } - · ┬ ┬ - · │ ╰── 'T' is declared here - · ╰── shadowed declaration is here + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:29] + 1 │ + 2 │ interface Foo {} + · ┬ + · ╰── 'A' is declared here + 3 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 4 │ ╰──── - help: Consider renaming 'T' to avoid shadowing the variable from the outer scope. + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:29] + 1 │ + 2 │ interface Foo {} + · ┬ + · ╰── 'A' is declared here + 3 │ interface A {} + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. - ⚠ eslint(no-shadow): 'E' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:6] - 1 │ enum E { A } function foo() { enum E { B } } - · ┬ ┬ - · │ ╰── 'E' is declared here - · ╰── shadowed declaration is here + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:24] + 1 │ + 2 │ type Foo = 1; + · ┬ + · ╰── 'A' is declared here + 3 │ interface A {} + · ┬ + · ╰── shadowed declaration is here + 4 │ ╰──── - help: Consider renaming 'E' to avoid shadowing the variable from the outer scope. + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. - ⚠ eslint(no-shadow): 'C' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:7] - 1 │ class C { } function foo() { class C { } } - · ┬ ┬ - · │ ╰── 'C' is declared here - · ╰── shadowed declaration is here + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:3:19] + 2 │ { + 3 │ type A = 1; + · ┬ + · ╰── 'A' is declared here + 4 │ } + 5 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 6 │ ╰──── - help: Consider renaming 'C' to avoid shadowing the variable from the outer scope. + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. - ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. - ╭─[no_shadow.tsx:2:22] + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:3:24] + 2 │ { + 3 │ interface A {} + · ┬ + · ╰── 'A' is declared here + 4 │ } + 5 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 6 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'args' is already a global variable. + ╭─[no_shadow.tsx:2:70] 1 │ - 2 │ import { Foo } from './foo'; - · ─┬─ - · ╰── shadowed declaration is here - 3 │ const x = Foo; - 4 │ function bar() { const Foo = 1; } - · ─┬─ - · ╰── 'Foo' is declared here - 5 │ + 2 │ function foo any>(fn: T, args: any[]) {} + · ──── + 3 │ ╰──── - help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + help: Consider renaming 'args' to avoid shadowing the global variable. - ⚠ eslint(no-shadow): 'T' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:14] - 1 │ function foo() { function bar() { } } - · ┬ ┬ - · │ ╰── 'T' is declared here - · ╰── shadowed declaration is here + ⚠ eslint(no-shadow): 'has' is already a global variable. + ╭─[no_shadow.tsx:2:29] + 1 │ + 2 │ declare const has = (environment: 'dev' | 'prod' | 'test') => boolean; + · ─── + 3 │ ╰──── - help: Consider renaming 'T' to avoid shadowing the variable from the outer scope. + help: Consider renaming 'has' to avoid shadowing the global variable. + + ⚠ eslint(no-shadow): 'has' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:29] + 1 │ + 2 │ declare const has: (environment: 'dev' | 'prod' | 'test') => boolean; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ const fn = (has: string) => {}; + · ─┬─ + · ╰── 'has' is declared here + 4 │ + ╰──── + help: Consider renaming 'has' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:22] + 1 │ + 2 │ const A = 2; + · ┬ + · ╰── shadowed declaration is here + 3 │ enum Test { + 4 │ A = 1, + · ──┬── + · ╰── 'A' is declared here + 5 │ B = A, + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. diff --git a/crates/oxc_linter/src/snapshots/eslint_no_shadow@typescript-eslint.snap b/crates/oxc_linter/src/snapshots/eslint_no_shadow@typescript-eslint.snap new file mode 100644 index 0000000000000..b263fb0d35b43 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/eslint_no_shadow@typescript-eslint.snap @@ -0,0 +1,608 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + + ⚠ eslint(no-shadow): 'T' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:18] + 1 │ + 2 │ type T = 1; + · ┬ + · ╰── shadowed declaration is here + 3 │ { + 4 │ type T = 2; + · ┬ + · ╰── 'T' is declared here + 5 │ } + ╰──── + help: Consider renaming 'T' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'T' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:18] + 1 │ + 2 │ type T = 1; + · ┬ + · ╰── shadowed declaration is here + 3 │ function foo(arg: T) {} + · ┬ + · ╰── 'T' is declared here + 4 │ + ╰──── + help: Consider renaming 'T' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'T' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:26] + 1 │ + 2 │ function foo() { + · ┬ + · ╰── shadowed declaration is here + 3 │ return function () {}; + · ┬ + · ╰── 'T' is declared here + 4 │ } + ╰──── + help: Consider renaming 'T' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'T' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:18] + 1 │ + 2 │ type T = string; + · ┬ + · ╰── shadowed declaration is here + 3 │ function foo void>(arg: T) {} + · ┬ + · ╰── 'T' is declared here + 4 │ + ╰──── + help: Consider renaming 'T' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:19] + 1 │ + 2 │ const x = 1; + · ┬ + · ╰── shadowed declaration is here + 3 │ { + 4 │ type x = string; + · ┬ + · ╰── 'x' is declared here + 5 │ } + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Foo' is already a global variable. + ╭─[no_shadow.tsx:2:18] + 1 │ + 2 │ type Foo = 1; + · ─── + 3 │ + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the global variable. + + ⚠ eslint(no-shadow): 'test' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:19] + 1 │ + 2 │ const test = 1; + · ──┬─ + · ╰── shadowed declaration is here + 3 │ type Fn = (test: string) => typeof test; + · ──┬─ + · ╰── 'test' is declared here + 4 │ + ╰──── + help: Consider renaming 'test' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Foo' is already a global variable. + ╭─[no_shadow.tsx:2:24] + 1 │ + 2 │ type Fn = (Foo: string) => typeof Foo; + · ─── + 3 │ + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the global variable. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:19] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ interface Test { + 5 │ (arg: string): typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 6 │ } + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:19] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ interface Test { + 5 │ p1(arg: string): typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 6 │ } + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:19] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare function test(arg: string): typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 5 │ + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:19] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare const test: (arg: string) => typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 5 │ + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:19] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare class Test { + 5 │ p1(arg: string): typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 6 │ } + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:19] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare const Test: { + 5 │ new (arg: string): typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 6 │ }; + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:19] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ type Bar = new (arg: number) => typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 5 │ + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'arg' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:19] + 1 │ + 2 │ const arg = 0; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare namespace Lib { + 5 │ function test(arg: string): typeof arg; + · ─┬─ + · ╰── 'arg' is declared here + 6 │ } + ╰──── + help: Consider renaming 'arg' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:27] + 1 │ + 2 │ import type { foo } from './foo'; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ function doThing(foo: number) {} + · ─┬─ + · ╰── 'foo' is declared here + 4 │ + ╰──── + help: Consider renaming 'foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:27] + 1 │ + 2 │ import { type foo } from './foo'; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ function doThing(foo: number) {} + · ─┬─ + · ╰── 'foo' is declared here + 4 │ + ╰──── + help: Consider renaming 'foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:22] + 1 │ + 2 │ import { foo } from './foo'; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ function doThing(foo: number, bar: number) {} + · ─┬─ + · ╰── 'foo' is declared here + 4 │ + ╰──── + help: Consider renaming 'foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:23] + 1 │ + 2 │ interface Foo {} + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare module 'bar' { + 5 │ export interface Foo { + · ─┬─ + · ╰── 'Foo' is declared here + 6 │ x: string; + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:27] + 1 │ + 2 │ import type { Foo } from 'bar'; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare module 'baz' { + 5 │ export interface Foo { + · ─┬─ + · ╰── 'Foo' is declared here + 6 │ x: string; + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:27] + 1 │ + 2 │ import { type Foo } from 'bar'; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare module 'baz' { + 5 │ export interface Foo { + · ─┬─ + · ╰── 'Foo' is declared here + 6 │ x: string; + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:17] + 1 │ + 2 │ let x = foo((x, y) => {}); + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + 3 │ let y; + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'y' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:29] + 1 │ + 2 │ let x = foo((x, y) => {}); + · ┬ + · ╰── 'y' is declared here + 3 │ let y; + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'y' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:17] + 1 │ + 2 │ let x = foo((x, y) => {}); + · ┬ ┬ + · │ ╰── 'x' is declared here + · ╰── shadowed declaration is here + 3 │ let y; + ╰──── + help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:22] + 1 │ + 2 │ type Foo = 1; + · ┬ + · ╰── 'A' is declared here + 3 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:27] + 1 │ + 2 │ interface Foo {} + · ┬ + · ╰── 'A' is declared here + 3 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:27] + 1 │ + 2 │ interface Foo {} + · ┬ + · ╰── 'A' is declared here + 3 │ interface A {} + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:22] + 1 │ + 2 │ type Foo = 1; + · ┬ + · ╰── 'A' is declared here + 3 │ interface A {} + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:3:20] + 2 │ { + 3 │ type A = 1; + · ┬ + · ╰── 'A' is declared here + 4 │ } + 5 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 6 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:3:25] + 2 │ { + 3 │ interface A {} + · ┬ + · ╰── 'A' is declared here + 4 │ } + 5 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 6 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:22] + 1 │ + 2 │ type Foo = 1; + · ┬ + · ╰── 'A' is declared here + 3 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:27] + 1 │ + 2 │ interface Foo {} + · ┬ + · ╰── 'A' is declared here + 3 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:27] + 1 │ + 2 │ interface Foo {} + · ┬ + · ╰── 'A' is declared here + 3 │ interface A {} + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:22] + 1 │ + 2 │ type Foo = 1; + · ┬ + · ╰── 'A' is declared here + 3 │ interface A {} + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:3:20] + 2 │ { + 3 │ type A = 1; + · ┬ + · ╰── 'A' is declared here + 4 │ } + 5 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 6 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:3:25] + 2 │ { + 3 │ interface A {} + · ┬ + · ╰── 'A' is declared here + 4 │ } + 5 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 6 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:22] + 1 │ + 2 │ type Foo = 1; + · ┬ + · ╰── 'A' is declared here + 3 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:27] + 1 │ + 2 │ interface Foo {} + · ┬ + · ╰── 'A' is declared here + 3 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:27] + 1 │ + 2 │ interface Foo {} + · ┬ + · ╰── 'A' is declared here + 3 │ interface A {} + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:22] + 1 │ + 2 │ type Foo = 1; + · ┬ + · ╰── 'A' is declared here + 3 │ interface A {} + · ┬ + · ╰── shadowed declaration is here + 4 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:3:20] + 2 │ { + 3 │ type A = 1; + · ┬ + · ╰── 'A' is declared here + 4 │ } + 5 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 6 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'A' is already declared in the upper scope. + ╭─[no_shadow.tsx:3:25] + 2 │ { + 3 │ interface A {} + · ┬ + · ╰── 'A' is declared here + 4 │ } + 5 │ type A = 1; + · ┬ + · ╰── shadowed declaration is here + 6 │ + ╰──── + help: Consider renaming 'A' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'args' is already a global variable. + ╭─[no_shadow.tsx:2:68] + 1 │ + 2 │ function foo any>(fn: T, args: any[]) {} + · ──── + 3 │ + ╰──── + help: Consider renaming 'args' to avoid shadowing the global variable. + + ⚠ eslint(no-shadow): 'has' is already a global variable. + ╭─[no_shadow.tsx:2:27] + 1 │ + 2 │ declare const has = (environment: 'dev' | 'prod' | 'test') => boolean; + · ─── + 3 │ + ╰──── + help: Consider renaming 'has' to avoid shadowing the global variable. + + ⚠ eslint(no-shadow): 'has' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:27] + 1 │ + 2 │ declare const has: (environment: 'dev' | 'prod' | 'test') => boolean; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ const fn = (has: string) => {}; + · ─┬─ + · ╰── 'has' is declared here + 4 │ + ╰──── + help: Consider renaming 'has' to avoid shadowing the variable from the outer scope. From d55ae6a60ce9080e385ea9497a576e48401df26e Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Wed, 11 Feb 2026 13:38:18 +0000 Subject: [PATCH 08/15] u --- .../oxc_linter/src/rules/eslint/no_shadow/mod.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs index 04c05507471cc..c3bd76dca8eab 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs @@ -238,7 +238,10 @@ impl NoShadow { false } - fn is_function_type_parameter_name_value_shadow(ctx: &LintContext, symbol_id: SymbolId) -> bool { + fn is_function_type_parameter_name_value_shadow( + ctx: &LintContext, + symbol_id: SymbolId, + ) -> bool { let declaration_id = ctx.scoping().symbol_declaration(symbol_id); ctx.nodes().ancestor_kinds(declaration_id).any(|ancestor_kind| { matches!( @@ -479,7 +482,11 @@ impl NoShadow { initializer.address() == ctx.nodes().kind(unwrapped_expression_id).address() } - fn is_init_pattern_node(ctx: &LintContext, symbol_id: SymbolId, shadowed_symbol_id: SymbolId) -> bool { + fn is_init_pattern_node( + ctx: &LintContext, + symbol_id: SymbolId, + shadowed_symbol_id: SymbolId, + ) -> bool { let scoping = ctx.scoping(); let variable_scope_id = scoping.symbol_scope_id(symbol_id); @@ -624,7 +631,8 @@ impl NoShadow { current_id = parent_id; } AstKind::ConditionalExpression(conditional_expression) => { - if conditional_expression.test.address() == ctx.nodes().kind(current_id).address() + if conditional_expression.test.address() + == ctx.nodes().kind(current_id).address() { break; } From 3d456a879024f844dceeef39a6689b03b115634c Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Wed, 11 Feb 2026 14:52:09 +0000 Subject: [PATCH 09/15] u --- .../src/rules/eslint/no_shadow/mod.rs | 184 ++++++++++-------- 1 file changed, 106 insertions(+), 78 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs index c3bd76dca8eab..5a83a2ec0b6d2 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs @@ -13,6 +13,7 @@ use oxc_ast::{ }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; +use oxc_semantic::Reference; use oxc_span::{GetSpan, Span}; use oxc_syntax::{ node::NodeId, @@ -108,24 +109,9 @@ impl Rule for NoShadow { for symbol_id in scoping.symbol_ids() { let symbol_name = scoping.symbol_ident(symbol_id); let symbol_name_str = symbol_name.as_str(); - let symbol_flags = scoping.symbol_flags(symbol_id); - let symbol_scope = scoping.symbol_scope_id(symbol_id); let symbol_span = scoping.symbol_span(symbol_id); - // Skip if in allow list - if self.allow.iter().any(|allowed| allowed.as_str() == symbol_name_str) { - continue; - } - - if symbol_name_str == "this" { - continue; - } - - if Self::is_declare_in_definition_file(ctx, symbol_id) { - continue; - } - - if Self::is_in_global_augmentation(ctx, symbol_id) { + if self.should_skip_symbol(ctx, symbol_id, symbol_name_str) { continue; } @@ -136,41 +122,20 @@ impl Rule for NoShadow { continue; } - let shadowed_symbol_id = scoping - .scope_ancestors(symbol_scope) - .skip(1) - .find_map(|scope_id| scoping.get_binding(scope_id, symbol_name)); - - if let Some(shadowed_symbol_id) = shadowed_symbol_id { - let shadowed_flags = scoping.symbol_flags(shadowed_symbol_id); - let shadowed_span = scoping.symbol_span(shadowed_symbol_id); - - if Self::is_function_name_initializer_exception(ctx, symbol_id, shadowed_symbol_id) - || (self.ignore_on_initialization - && Self::is_init_pattern_node(ctx, symbol_id, shadowed_symbol_id)) - || (self.hoist != HoistOption::All - && self.is_in_tdz(ctx, symbol_id, shadowed_symbol_id)) - || self.should_ignore_shadow( - ctx, - symbol_id, - symbol_flags, - shadowed_symbol_id, - shadowed_flags, - ) - || Self::is_external_declaration_merging(ctx, symbol_id, shadowed_symbol_id) - { - continue; + if let Some(shadowed_symbol_id) = Self::find_shadowed_symbol_id(ctx, symbol_id) { + if !self.should_ignore_shadowed_symbol(ctx, symbol_id, shadowed_symbol_id) { + let shadowed_span = scoping.symbol_span(shadowed_symbol_id); + ctx.diagnostic(no_shadow_diagnostic( + symbol_span, + symbol_name_str, + shadowed_span, + )); } - - ctx.diagnostic(no_shadow_diagnostic(symbol_span, symbol_name_str, shadowed_span)); continue; } - if self.builtin_globals && is_builtin_global_name(ctx, symbol_name_str) { - if self.should_ignore_global_shadow(ctx, symbol_id, symbol_flags) { - continue; - } - + let symbol_flags = scoping.symbol_flags(symbol_id); + if self.is_builtin_global_shadow(ctx, symbol_id, symbol_name_str, symbol_flags) { ctx.diagnostic(no_shadow_global_diagnostic(symbol_span, symbol_name_str)); } } @@ -178,6 +143,66 @@ impl Rule for NoShadow { } impl NoShadow { + fn should_skip_symbol( + &self, + ctx: &LintContext, + symbol_id: SymbolId, + symbol_name: &str, + ) -> bool { + self.allow.iter().any(|allowed| allowed.as_str() == symbol_name) + || symbol_name == "this" + || Self::is_declare_in_definition_file(ctx, symbol_id) + || Self::is_in_global_augmentation(ctx, symbol_id) + } + + fn find_shadowed_symbol_id(ctx: &LintContext, symbol_id: SymbolId) -> Option { + let scoping = ctx.scoping(); + let symbol_name = scoping.symbol_ident(symbol_id); + let symbol_scope = scoping.symbol_scope_id(symbol_id); + + scoping + .scope_ancestors(symbol_scope) + .skip(1) + .find_map(|scope_id| scoping.get_binding(scope_id, symbol_name)) + } + + fn should_ignore_shadowed_symbol( + &self, + ctx: &LintContext, + symbol_id: SymbolId, + shadowed_symbol_id: SymbolId, + ) -> bool { + let scoping = ctx.scoping(); + let symbol_flags = scoping.symbol_flags(symbol_id); + let shadowed_flags = scoping.symbol_flags(shadowed_symbol_id); + + Self::is_function_name_initializer_exception(ctx, symbol_id, shadowed_symbol_id) + || (self.ignore_on_initialization + && Self::is_init_pattern_node(ctx, symbol_id, shadowed_symbol_id)) + || (self.hoist != HoistOption::All + && self.is_in_tdz(ctx, symbol_id, shadowed_symbol_id)) + || self.should_ignore_shadow( + ctx, + symbol_id, + symbol_flags, + shadowed_symbol_id, + shadowed_flags, + ) + || Self::is_external_declaration_merging(ctx, symbol_id, shadowed_symbol_id) + } + + fn is_builtin_global_shadow( + &self, + ctx: &LintContext, + symbol_id: SymbolId, + symbol_name: &str, + symbol_flags: SymbolFlags, + ) -> bool { + self.builtin_globals + && is_builtin_global_name(ctx, symbol_name) + && !self.should_ignore_global_shadow(ctx, symbol_id, symbol_flags) + } + fn should_ignore_global_shadow( &self, ctx: &LintContext, @@ -200,17 +225,10 @@ impl NoShadow { shadowed_flags: SymbolFlags, ) -> bool { // Ignore when one side is type-only and the other side is value-only. - if self.ignore_type_value_shadow { - let symbol_is_value = symbol_flags.can_be_referenced_by_value(); - let shadowed_is_value = if shadowed_flags.is_type_import() { - false - } else { - shadowed_flags.can_be_referenced_by_value() - }; - - if symbol_is_value != shadowed_is_value { - return true; - } + if self.ignore_type_value_shadow + && Self::is_type_value_shadow_pair(symbol_flags, shadowed_flags) + { + return true; } if self.ignore_function_type_parameter_name_value_shadow @@ -224,18 +242,33 @@ impl NoShadow { } // Value imports that are only used as types are allowed to be shadowed by values. - if shadowed_flags.contains(SymbolFlags::Import) { - let references: Vec<_> = - ctx.scoping().get_resolved_references(shadowed_symbol_id).collect(); - let has_refs = !references.is_empty(); - let all_type_refs = references.iter().all(|r| r.is_type()); - - if has_refs && all_type_refs && !is_type_only(symbol_flags) { - return true; - } + Self::is_value_import_used_only_as_type( + ctx, + symbol_flags, + shadowed_symbol_id, + shadowed_flags, + ) + } + + fn is_type_value_shadow_pair(symbol_flags: SymbolFlags, shadowed_flags: SymbolFlags) -> bool { + let symbol_is_value = symbol_flags.can_be_referenced_by_value(); + let shadowed_is_value = + !shadowed_flags.is_type_import() && shadowed_flags.can_be_referenced_by_value(); + symbol_is_value != shadowed_is_value + } + + fn is_value_import_used_only_as_type( + ctx: &LintContext, + symbol_flags: SymbolFlags, + shadowed_symbol_id: SymbolId, + shadowed_flags: SymbolFlags, + ) -> bool { + if !shadowed_flags.contains(SymbolFlags::Import) || is_type_only(symbol_flags) { + return false; } - false + let mut references = ctx.scoping().get_resolved_references(shadowed_symbol_id).peekable(); + references.peek().is_some() && references.all(Reference::is_type) } fn is_function_type_parameter_name_value_shadow( @@ -269,16 +302,11 @@ impl NoShadow { } let declaration_id = ctx.scoping().symbol_declaration(symbol_id); - let mut type_parameter_decl_id = None; - - for ancestor_id in ctx.nodes().ancestor_ids(declaration_id) { - if matches!(ctx.nodes().kind(ancestor_id), AstKind::TSTypeParameterDeclaration(_)) { - type_parameter_decl_id = Some(ancestor_id); - break; - } - } - - let Some(type_parameter_decl_id) = type_parameter_decl_id else { + let Some(type_parameter_decl_id) = + ctx.nodes().ancestor_ids(declaration_id).find(|&ancestor_id| { + matches!(ctx.nodes().kind(ancestor_id), AstKind::TSTypeParameterDeclaration(_)) + }) + else { return false; }; From 970556dad3301174e7325da96ebe1861365757e4 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Sun, 15 Feb 2026 14:56:52 +0000 Subject: [PATCH 10/15] fix edge case with interface merging --- .../src/rules/eslint/no_shadow/mod.rs | 12 +++++--- .../src/rules/eslint/no_shadow/tests.rs | 26 ++++++++++++++++ .../eslint_no_shadow@typescript-eslint.snap | 30 +++++++++++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs index 5a83a2ec0b6d2..0727dde3bcff2 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs @@ -437,6 +437,10 @@ impl NoShadow { symbol_id: SymbolId, shadowed_symbol_id: SymbolId, ) -> bool { + if !Self::is_hoisted_type_declaration(ctx, symbol_id) { + return false; + } + let shadowed_flags = ctx.scoping().symbol_flags(shadowed_symbol_id); if !shadowed_flags.is_type_import() { return false; @@ -456,14 +460,14 @@ impl NoShadow { let declaration_id = ctx.scoping().symbol_declaration(symbol_id); let module_name = ctx.nodes().ancestor_kinds(declaration_id).find_map(|kind| { - if let AstKind::TSModuleDeclaration(module_decl) = kind { + if let AstKind::TSModuleDeclaration(module_decl) = kind + && module_decl.declare + { match &module_decl.id { - TSModuleDeclarationName::Identifier(identifier) => { - Some(identifier.name.as_str()) - } TSModuleDeclarationName::StringLiteral(string_literal) => { Some(string_literal.value.as_str()) } + _ => None, } } else { None diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs index 993a6b3fe906c..d1dfe8df3dfbb 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/tests.rs @@ -3402,6 +3402,32 @@ fn test_typescript_eslint() { None, None, ), + ( + " + import type { Foo } from 'bar'; + + declare module 'bar' { + export class Foo {} + } + ", + Some(serde_json::json!([{ "ignoreTypeValueShadow": false }])), + None, + None, + ), + ( + " + import type { Foo } from 'bar'; + + module bar { + export interface Foo { + x: string; + } + } + ", + None, + None, + None, + ), ( " let x = foo((x, y) => {}); diff --git a/crates/oxc_linter/src/snapshots/eslint_no_shadow@typescript-eslint.snap b/crates/oxc_linter/src/snapshots/eslint_no_shadow@typescript-eslint.snap index b263fb0d35b43..41153aca28420 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_shadow@typescript-eslint.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_shadow@typescript-eslint.snap @@ -301,6 +301,36 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:27] + 1 │ + 2 │ import type { Foo } from 'bar'; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ declare module 'bar' { + 5 │ export class Foo {} + · ─┬─ + · ╰── 'Foo' is declared here + 6 │ } + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + + ⚠ eslint(no-shadow): 'Foo' is already declared in the upper scope. + ╭─[no_shadow.tsx:2:27] + 1 │ + 2 │ import type { Foo } from 'bar'; + · ─┬─ + · ╰── shadowed declaration is here + 3 │ + 4 │ module bar { + 5 │ export interface Foo { + · ─┬─ + · ╰── 'Foo' is declared here + 6 │ x: string; + ╰──── + help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. + ⚠ eslint(no-shadow): 'x' is already declared in the upper scope. ╭─[no_shadow.tsx:2:17] 1 │ From 41a852b078168094f6edf82cb8a07f5e9ac022ab Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Sun, 15 Feb 2026 14:57:34 +0000 Subject: [PATCH 11/15] fix doc --- crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs index 0727dde3bcff2..61b3ea0885659 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs @@ -83,14 +83,6 @@ declare_oxc_lint!( /// var y = 2; // different name, no shadowing /// } /// ``` - /// - /// ### TypeScript - /// - /// This rule supports TypeScript-specific options: - /// - `ignoreTypeValueShadow`: When `true` (default), ignores cases where a type and a value - /// have the same name (e.g., `type Foo = string; const Foo = 'bar';`). - /// - `ignoreFunctionTypeParameterNameValueShadow`: When `true` (default), ignores cases where - /// a function type parameter shadows a value (e.g., `const T = 1; function foo() {}`). NoShadow, eslint, suspicious, From f7aaf6c790b973cd9a9e4898d4cd50cf369851de Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Sun, 15 Feb 2026 14:58:09 +0000 Subject: [PATCH 12/15] remove impossible comparison --- crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs index 61b3ea0885659..707071ac87e15 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs @@ -142,7 +142,6 @@ impl NoShadow { symbol_name: &str, ) -> bool { self.allow.iter().any(|allowed| allowed.as_str() == symbol_name) - || symbol_name == "this" || Self::is_declare_in_definition_file(ctx, symbol_id) || Self::is_in_global_augmentation(ctx, symbol_id) } From df26373f1ead74dbd5633b9b6d26616931668c68 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Sun, 15 Feb 2026 14:58:41 +0000 Subject: [PATCH 13/15] only fetch if neeeded --- crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs index 707071ac87e15..e92e5194dccba 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs @@ -164,8 +164,6 @@ impl NoShadow { shadowed_symbol_id: SymbolId, ) -> bool { let scoping = ctx.scoping(); - let symbol_flags = scoping.symbol_flags(symbol_id); - let shadowed_flags = scoping.symbol_flags(shadowed_symbol_id); Self::is_function_name_initializer_exception(ctx, symbol_id, shadowed_symbol_id) || (self.ignore_on_initialization @@ -175,9 +173,9 @@ impl NoShadow { || self.should_ignore_shadow( ctx, symbol_id, - symbol_flags, + scoping.symbol_flags(symbol_id), shadowed_symbol_id, - shadowed_flags, + scoping.symbol_flags(shadowed_symbol_id), ) || Self::is_external_declaration_merging(ctx, symbol_id, shadowed_symbol_id) } From 2139ef97eb187f741941786d97b9323d46af9637 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Sun, 15 Feb 2026 14:59:28 +0000 Subject: [PATCH 14/15] fix lint --- crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs index e92e5194dccba..e01fcc0b2354c 100644 --- a/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs +++ b/crates/oxc_linter/src/rules/eslint/no_shadow/mod.rs @@ -456,7 +456,7 @@ impl NoShadow { TSModuleDeclarationName::StringLiteral(string_literal) => { Some(string_literal.value.as_str()) } - _ => None, + TSModuleDeclarationName::Identifier(_) => None, } } else { None From 90a6b92c3ff11e7e9cc00d35addf46b48d472caf Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Sun, 15 Feb 2026 14:59:34 +0000 Subject: [PATCH 15/15] remove unused snap --- .../src/snapshots/typescript_no_shadow.snap | 192 ------------------ 1 file changed, 192 deletions(-) delete mode 100644 crates/oxc_linter/src/snapshots/typescript_no_shadow.snap diff --git a/crates/oxc_linter/src/snapshots/typescript_no_shadow.snap b/crates/oxc_linter/src/snapshots/typescript_no_shadow.snap deleted file mode 100644 index 484610b51b370..0000000000000 --- a/crates/oxc_linter/src/snapshots/typescript_no_shadow.snap +++ /dev/null @@ -1,192 +0,0 @@ ---- -source: crates/oxc_linter/src/tester.rs ---- - - ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:7] - 1 │ const x = 1; function foo() { const x = 2; } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:7] - 1 │ const x = 1; { const x = 2; } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:7] - 1 │ const x = 1; function foo(x) { } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:24] - 1 │ function foo() { const x = 1; function bar() { const x = 2; } } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:7] - 1 │ const x = 1; const foo = () => { const x = 2; }; - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:7] - 1 │ const x = 1; class Foo { method() { const x = 2; } } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:5] - 1 │ let x = 1; { let x = 2; } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:5] - 1 │ var x = 1; function foo() { var x = 2; } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'Foo' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:6] - 1 │ type Foo = string; { type Foo = number; } - · ─┬─ ─┬─ - · │ ╰── 'Foo' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'Foo' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:11] - 1 │ interface Foo { x: number } { interface Foo { y: string } } - · ─┬─ ─┬─ - · │ ╰── 'Foo' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'e' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:7] - 1 │ const e = 1; try { } catch (e) { } - · ┬ ┬ - · │ ╰── 'e' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'e' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'i' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:7] - 1 │ const i = 1; for (let i = 0; i < 10; i++) { } - · ┬ ┬ - · │ ╰── 'i' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'i' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:7] - 1 │ const x = 1; { const { x } = { x: 2 }; } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:7] - 1 │ const x = 1; { const [x] = [2]; } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'Foo' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:6] - 1 │ type Foo = string; { const Foo = 'bar'; } - · ─┬─ ─┬─ - · │ ╰── 'Foo' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'Foo' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:11] - 1 │ interface Foo { x: number }; { const Foo = { x: 1 } }; - · ─┬─ ─┬─ - · │ ╰── 'Foo' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'Foo' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'T' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:7] - 1 │ const T = 1; function foo() { } - · ┬ ┬ - · │ ╰── 'T' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'T' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:45] - 1 │ function outer() { function inner() { const x = 1; } var x = 2; } - · ┬ ┬ - · │ ╰── shadowed declaration is here - · ╰── 'x' is declared here - ╰──── - help: Consider renaming 'x' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'foo' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:45] - 1 │ function outer() { function inner() { const foo = 1; } function foo() {} } - · ─┬─ ─┬─ - · │ ╰── shadowed declaration is here - · ╰── 'foo' is declared here - ╰──── - help: Consider renaming 'foo' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'foo' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:45] - 1 │ function outer() { function inner() { const foo = 1; } function foo() {} } - · ─┬─ ─┬─ - · │ ╰── shadowed declaration is here - · ╰── 'foo' is declared here - ╰──── - help: Consider renaming 'foo' to avoid shadowing the variable from the outer scope. - - ⚠ typescript-eslint(no-shadow): 'x' is already declared in the upper scope. - ╭─[no_shadow.tsx:1:24] - 1 │ function outer() { var x = 2; function inner() { const x = 1; } } - · ┬ ┬ - · │ ╰── 'x' is declared here - · ╰── shadowed declaration is here - ╰──── - help: Consider renaming 'x' to avoid shadowing the variable from the outer scope.