diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 8f8ef3195..ee2662f10 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -347,6 +347,7 @@ pub enum PropertyKey<'a> { } impl<'a> PropertyKey<'a> { + // FIXME: this would ideally return Option<&'a Atom> or a Cow pub fn static_name(&self) -> Option { match self { Self::Identifier(ident) => Some(ident.name.clone()), diff --git a/crates/oxc_linter/src/rules/eslint/no_dupe_keys.rs b/crates/oxc_linter/src/rules/eslint/no_dupe_keys.rs index 7b0c15115..d2454bedc 100644 --- a/crates/oxc_linter/src/rules/eslint/no_dupe_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/no_dupe_keys.rs @@ -1,5 +1,5 @@ use oxc_ast::{ - ast::{ObjectPropertyKind, PropertyKind}, + ast::{ObjectPropertyKind, PropertyKey, PropertyKind, Expression}, AstKind, }; use oxc_diagnostics::{ @@ -9,6 +9,7 @@ use oxc_diagnostics::{ use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; use rustc_hash::FxHashMap; +use lazy_static::lazy_static; use crate::{ast_util::calculate_hash, context::LintContext, rule::Rule, AstNode}; @@ -42,29 +43,50 @@ declare_oxc_lint!( impl Rule for NoDupeKeys { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - if let AstKind::ObjectExpression(obj_expr) = node.kind() { - let mut map = FxHashMap::default(); - for prop in &obj_expr.properties { - if let ObjectPropertyKind::ObjectProperty(prop) = prop { - if let Some(key_name) = prop.key.static_name().as_ref() { - let hash = calculate_hash(key_name); - if let Some((prev_kind, prev_span)) = - map.insert(hash, (prop.kind, prop.key.span())) - { - if prev_kind == PropertyKind::Init - || prop.kind == PropertyKind::Init - || prev_kind == prop.kind - { - ctx.diagnostic(NoDupeKeysDiagnostic(prev_span, prop.key.span())); - } - } - } + let AstKind::ObjectExpression(obj_expr) = node.kind() else { return }; + let mut map = FxHashMap::default(); + for prop in &obj_expr.properties { + let ObjectPropertyKind::ObjectProperty(prop) = prop else { continue }; + let Some(hash) = calculate_property_kind_hash(&prop.key) else { continue }; + if let Some((prev_kind, prev_span)) = map.insert(hash, (prop.kind, prop.key.span())) { + if prev_kind == PropertyKind::Init + || prop.kind == PropertyKind::Init + || prev_kind == prop.kind + { + ctx.diagnostic(NoDupeKeysDiagnostic(prev_span, prop.key.span())); } } } } } +// todo: should this be located within oxc_ast? +fn calculate_property_kind_hash(key: &PropertyKey) -> Option { + lazy_static! { + static ref NULL_HASH: u64 = calculate_hash(&"null"); + } + + match key { + PropertyKey::Identifier(ident) => Some(calculate_hash(&ident)), + PropertyKey::PrivateIdentifier(_) => None, + PropertyKey::Expression(expr) => match expr { + Expression::StringLiteral(lit) => Some(calculate_hash(&lit.value)), + // note: hashes won't work as expected if these aren't strings. Save + // NumberLiteral I don't think this should be too much of a problem + // b/c most people don't use `null`, regexes, etc. as object + // property keys when writing real code. + Expression::RegExpLiteral(lit) => Some(calculate_hash(&lit.regex.to_string())), + Expression::NumberLiteral(lit) => Some(calculate_hash(&lit.value.to_string())), + Expression::BigintLiteral(lit) => Some(calculate_hash(&lit.value.to_string())), + Expression::NullLiteral(_) => Some(*NULL_HASH), + Expression::TemplateLiteral(lit) => { + lit.expressions.is_empty().then(|| lit.quasi()).flatten().map(calculate_hash) + } + _ => None, + }, + } +} + #[test] fn test() { use crate::tester::Tester; diff --git a/crates/oxc_linter/src/rules/eslint/no_global_assign.rs b/crates/oxc_linter/src/rules/eslint/no_global_assign.rs index 237f842b8..66e291315 100644 --- a/crates/oxc_linter/src/rules/eslint/no_global_assign.rs +++ b/crates/oxc_linter/src/rules/eslint/no_global_assign.rs @@ -53,25 +53,15 @@ impl Rule for NoGlobalAssign { } fn run_once(&self, ctx: &LintContext) { - let symbol_table = ctx.semantic().symbols(); + let symbol_table = ctx.symbols(); for reference_id_list in ctx.scopes().root_unresolved_references().values() { for &reference_id in reference_id_list { let reference = symbol_table.get_reference(reference_id); - if symbol_table.is_global_reference(reference_id) && reference.is_write() { - let name = reference.name().clone(); - let mut is_global_assign = false; - if let Some(&value) = BUILTINS.get(&name) { - if !value { - is_global_assign = true; - } - } + if reference.is_write() && symbol_table.is_global_reference(reference_id) { + let name = reference.name(); - if self.excludes.contains(&name.clone()) { - is_global_assign = false; - } - - if is_global_assign { - ctx.diagnostic(NoGlobalAssignDiagnostic(name, reference.span())); + if !self.excludes.contains(name) && BUILTINS.contains_key(name) { + ctx.diagnostic(NoGlobalAssignDiagnostic(name.clone(), reference.span())); } } } diff --git a/crates/oxc_linter/src/rules/eslint/no_loss_of_precision.rs b/crates/oxc_linter/src/rules/eslint/no_loss_of_precision.rs index b8b71f894..36df1b88b 100644 --- a/crates/oxc_linter/src/rules/eslint/no_loss_of_precision.rs +++ b/crates/oxc_linter/src/rules/eslint/no_loss_of_precision.rs @@ -59,8 +59,7 @@ impl<'a> PartialEq for NormalizedNum<'a> { if self.coefficient == "0" { true } else { - self.magnitude == other.magnitude - && self.coefficient == other.coefficient + self.magnitude == other.magnitude && self.coefficient == other.coefficient } } } @@ -74,11 +73,6 @@ impl NoLossOfPrecision { } } - fn base_ten(node: &'_ NumberLiteral) -> bool { - let prefixes = ["0x", "0X", "0b", "0B", "0o", "0O"]; - !prefixes.iter().any(|prefix| node.raw.starts_with(prefix)) - } - fn not_base_ten_loses_precision(node: &'_ NumberLiteral) -> bool { let raw = Self::get_raw(node).to_uppercase(); #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] @@ -140,10 +134,7 @@ impl NoLossOfPrecision { fn normalize_int(num: Cow<'_, str>) -> NormalizedNum<'_> { // specially deal with 0 if num == "0" { - return NormalizedNum { - magnitude: 0, - coefficient: Cow::Borrowed("0"), - }; + return NormalizedNum { magnitude: 0, coefficient: Cow::Borrowed("0") }; } #[allow(clippy::cast_possible_wrap)] @@ -163,10 +154,7 @@ impl NoLossOfPrecision { // unwrap here will never panic, we guarantee the input contains a `.` #[allow(clippy::cast_possible_wrap)] let magnitude = (trimmed_float.find('.').unwrap() - 1) as isize; - NormalizedNum { - coefficient: Cow::Owned(trimmed_float.replace('.', "")), - magnitude, - } + NormalizedNum { coefficient: Cow::Owned(trimmed_float.replace('.', "")), magnitude } }, |stripped| { let decimal_digits = stripped.len(); @@ -205,7 +193,7 @@ impl NoLossOfPrecision { } pub fn lose_precision(node: &'_ NumberLiteral) -> bool { - if Self::base_ten(node) { + if node.base.is_base_10() { Self::base_ten_loses_precision(node) } else { Self::not_base_ten_loses_precision(node) diff --git a/crates/oxc_syntax/src/lib.rs b/crates/oxc_syntax/src/lib.rs index f671052c7..3488c2d2a 100644 --- a/crates/oxc_syntax/src/lib.rs +++ b/crates/oxc_syntax/src/lib.rs @@ -17,3 +17,9 @@ pub enum NumberBase { Octal, Hex, } + +impl NumberBase { + pub fn is_base_10(&self) -> bool { + matches!(self, Self::Float | Self::Decimal) + } +}