mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +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> {
|
impl<'a> PropertyKey<'a> {
|
||||||
|
// FIXME: this would ideally return Option<&'a Atom> or a Cow
|
||||||
pub fn static_name(&self) -> Option<Atom> {
|
pub fn static_name(&self) -> Option<Atom> {
|
||||||
match self {
|
match self {
|
||||||
Self::Identifier(ident) => Some(ident.name.clone()),
|
Self::Identifier(ident) => Some(ident.name.clone()),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use oxc_ast::{
|
use oxc_ast::{
|
||||||
ast::{ObjectPropertyKind, PropertyKind},
|
ast::{ObjectPropertyKind, PropertyKey, PropertyKind, Expression},
|
||||||
AstKind,
|
AstKind,
|
||||||
};
|
};
|
||||||
use oxc_diagnostics::{
|
use oxc_diagnostics::{
|
||||||
|
|
@ -9,6 +9,7 @@ use oxc_diagnostics::{
|
||||||
use oxc_macros::declare_oxc_lint;
|
use oxc_macros::declare_oxc_lint;
|
||||||
use oxc_span::{GetSpan, Span};
|
use oxc_span::{GetSpan, Span};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
use crate::{ast_util::calculate_hash, context::LintContext, rule::Rule, AstNode};
|
use crate::{ast_util::calculate_hash, context::LintContext, rule::Rule, AstNode};
|
||||||
|
|
||||||
|
|
@ -42,15 +43,12 @@ declare_oxc_lint!(
|
||||||
|
|
||||||
impl Rule for NoDupeKeys {
|
impl Rule for NoDupeKeys {
|
||||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||||
if let AstKind::ObjectExpression(obj_expr) = node.kind() {
|
let AstKind::ObjectExpression(obj_expr) = node.kind() else { return };
|
||||||
let mut map = FxHashMap::default();
|
let mut map = FxHashMap::default();
|
||||||
for prop in &obj_expr.properties {
|
for prop in &obj_expr.properties {
|
||||||
if let ObjectPropertyKind::ObjectProperty(prop) = prop {
|
let ObjectPropertyKind::ObjectProperty(prop) = prop else { continue };
|
||||||
if let Some(key_name) = prop.key.static_name().as_ref() {
|
let Some(hash) = calculate_property_kind_hash(&prop.key) else { continue };
|
||||||
let hash = calculate_hash(key_name);
|
if let Some((prev_kind, prev_span)) = map.insert(hash, (prop.kind, prop.key.span())) {
|
||||||
if let Some((prev_kind, prev_span)) =
|
|
||||||
map.insert(hash, (prop.kind, prop.key.span()))
|
|
||||||
{
|
|
||||||
if prev_kind == PropertyKind::Init
|
if prev_kind == PropertyKind::Init
|
||||||
|| prop.kind == PropertyKind::Init
|
|| prop.kind == PropertyKind::Init
|
||||||
|| prev_kind == prop.kind
|
|| prev_kind == prop.kind
|
||||||
|
|
@ -60,8 +58,32 @@ impl Rule for NoDupeKeys {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,25 +53,15 @@ impl Rule for NoGlobalAssign {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_once(&self, ctx: &LintContext) {
|
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_list in ctx.scopes().root_unresolved_references().values() {
|
||||||
for &reference_id in reference_id_list {
|
for &reference_id in reference_id_list {
|
||||||
let reference = symbol_table.get_reference(reference_id);
|
let reference = symbol_table.get_reference(reference_id);
|
||||||
if symbol_table.is_global_reference(reference_id) && reference.is_write() {
|
if reference.is_write() && symbol_table.is_global_reference(reference_id) {
|
||||||
let name = reference.name().clone();
|
let name = reference.name();
|
||||||
let mut is_global_assign = false;
|
|
||||||
if let Some(&value) = BUILTINS.get(&name) {
|
|
||||||
if !value {
|
|
||||||
is_global_assign = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.excludes.contains(&name.clone()) {
|
if !self.excludes.contains(name) && BUILTINS.contains_key(name) {
|
||||||
is_global_assign = false;
|
ctx.diagnostic(NoGlobalAssignDiagnostic(name.clone(), reference.span()));
|
||||||
}
|
|
||||||
|
|
||||||
if is_global_assign {
|
|
||||||
ctx.diagnostic(NoGlobalAssignDiagnostic(name, reference.span()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,7 @@ impl<'a> PartialEq for NormalizedNum<'a> {
|
||||||
if self.coefficient == "0" {
|
if self.coefficient == "0" {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
self.magnitude == other.magnitude
|
self.magnitude == other.magnitude && self.coefficient == other.coefficient
|
||||||
&& 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 {
|
fn not_base_ten_loses_precision(node: &'_ NumberLiteral) -> bool {
|
||||||
let raw = Self::get_raw(node).to_uppercase();
|
let raw = Self::get_raw(node).to_uppercase();
|
||||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||||
|
|
@ -140,10 +134,7 @@ impl NoLossOfPrecision {
|
||||||
fn normalize_int(num: Cow<'_, str>) -> NormalizedNum<'_> {
|
fn normalize_int(num: Cow<'_, str>) -> NormalizedNum<'_> {
|
||||||
// specially deal with 0
|
// specially deal with 0
|
||||||
if num == "0" {
|
if num == "0" {
|
||||||
return NormalizedNum {
|
return NormalizedNum { magnitude: 0, coefficient: Cow::Borrowed("0") };
|
||||||
magnitude: 0,
|
|
||||||
coefficient: Cow::Borrowed("0"),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::cast_possible_wrap)]
|
#[allow(clippy::cast_possible_wrap)]
|
||||||
|
|
@ -163,10 +154,7 @@ impl NoLossOfPrecision {
|
||||||
// unwrap here will never panic, we guarantee the input contains a `.`
|
// unwrap here will never panic, we guarantee the input contains a `.`
|
||||||
#[allow(clippy::cast_possible_wrap)]
|
#[allow(clippy::cast_possible_wrap)]
|
||||||
let magnitude = (trimmed_float.find('.').unwrap() - 1) as isize;
|
let magnitude = (trimmed_float.find('.').unwrap() - 1) as isize;
|
||||||
NormalizedNum {
|
NormalizedNum { coefficient: Cow::Owned(trimmed_float.replace('.', "")), magnitude }
|
||||||
coefficient: Cow::Owned(trimmed_float.replace('.', "")),
|
|
||||||
magnitude,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|stripped| {
|
|stripped| {
|
||||||
let decimal_digits = stripped.len();
|
let decimal_digits = stripped.len();
|
||||||
|
|
@ -205,7 +193,7 @@ impl NoLossOfPrecision {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lose_precision(node: &'_ NumberLiteral) -> bool {
|
pub fn lose_precision(node: &'_ NumberLiteral) -> bool {
|
||||||
if Self::base_ten(node) {
|
if node.base.is_base_10() {
|
||||||
Self::base_ten_loses_precision(node)
|
Self::base_ten_loses_precision(node)
|
||||||
} else {
|
} else {
|
||||||
Self::not_base_ten_loses_precision(node)
|
Self::not_base_ten_loses_precision(node)
|
||||||
|
|
|
||||||
|
|
@ -17,3 +17,9 @@ pub enum NumberBase {
|
||||||
Octal,
|
Octal,
|
||||||
Hex,
|
Hex,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl NumberBase {
|
||||||
|
pub fn is_base_10(&self) -> bool {
|
||||||
|
matches!(self, Self::Float | Self::Decimal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue