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:
Don Isaac 2023-07-29 00:02:53 -04:00 committed by GitHub
parent 976aa28f67
commit 6628fc8d9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 56 additions and 49 deletions

View file

@ -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()),

View file

@ -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,29 +43,50 @@ 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)) = if prev_kind == PropertyKind::Init
map.insert(hash, (prop.kind, prop.key.span())) || prop.kind == PropertyKind::Init
{ || prev_kind == prop.kind
if prev_kind == PropertyKind::Init {
|| prop.kind == PropertyKind::Init ctx.diagnostic(NoDupeKeysDiagnostic(prev_span, prop.key.span()));
|| 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] #[test]
fn test() { fn test() {
use crate::tester::Tester; use crate::tester::Tester;

View file

@ -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()));
} }
} }
} }

View file

@ -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)

View file

@ -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)
}
}