mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
perf(linter): reduce mallocs (#654)
Reduces mallocs via `clones()` (particularly of `Atom`s), and vec allocations. Affects the following rules: - [x] eslint/no-dupe-keys - [x] eslint/no-global-assign - [x] eslint/no-loss-of-precision _(only partially optimized, needs more work later)_ ~- [ ] typescript-eslint/ajacent-overload-signatures~ (_will be addressed in following pr_)
This commit is contained in:
parent
976aa28f67
commit
6628fc8d9c
5 changed files with 56 additions and 49 deletions
|
|
@ -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<Atom> {
|
||||
match self {
|
||||
Self::Identifier(ident) => Some(ident.name.clone()),
|
||||
|
|
|
|||
|
|
@ -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<u64> {
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -17,3 +17,9 @@ pub enum NumberBase {
|
|||
Octal,
|
||||
Hex,
|
||||
}
|
||||
|
||||
impl NumberBase {
|
||||
pub fn is_base_10(&self) -> bool {
|
||||
matches!(self, Self::Float | Self::Decimal)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue