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> {
// 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()),

View file

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

View file

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

View file

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

View file

@ -17,3 +17,9 @@ pub enum NumberBase {
Octal,
Hex,
}
impl NumberBase {
pub fn is_base_10(&self) -> bool {
matches!(self, Self::Float | Self::Decimal)
}
}