perf(linter): reduce allocs in no-dupe-keys (#28472)

Instead of creating a new hash map for every object, store properties in
an inline smallvec first before upgrading to a hashmap once there are a
large enough amount of properties.

on local fixtures:

Allocations: 9,264 → 224 (−97.6%).
Allocated bytes: −81.0%.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Cam McHenryandCopilot authored and GitHub committed 2026-10-30 12:26:30 -04:00
1 parent 6f3e982b81
commit b138c20bf3
1 file changed
+23 -2
@@ -8,6 +8,7 @@ use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};
use rustc_hash::{FxBuildHasher, FxHashMap};
use smallvec::SmallVec;
use crate::{AstNode, context::LintContext, rule::Rule};
@@ -71,6 +72,8 @@ declare_oxc_lint!(
short_description = "Disallow duplicate keys in object literals.",
);
const INLINE_CAPACITY: usize = 8;
impl Rule for NoDupeKeys {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::ObjectExpression(obj_expr) = node.kind() else {
@@ -80,7 +83,14 @@ impl Rule for NoDupeKeys {
if len <= 1 {
return;
}
let mut map = FxHashMap::with_capacity_and_hasher(len, FxBuildHasher);
let mut inline: SmallVec<
[(StaticPropertyName<'_>, (PropertyKind, Span)); INLINE_CAPACITY],
> = SmallVec::new();
// Bound linear searches to small objects; retain hashing for larger ones.
let mut map = FxHashMap::with_capacity_and_hasher(
if len > INLINE_CAPACITY { len } else { 0 },
FxBuildHasher,
);
for prop in &obj_expr.properties {
let ObjectPropertyKind::ObjectProperty(prop) = prop else {
continue;
@@ -91,7 +101,18 @@ impl Rule for NoDupeKeys {
if is_proto_setter_property(prop, &name) {
continue;
}
if let Some((prev_kind, prev_span)) = map.insert(name, (prop.kind, prop.key.span()))
let value = (prop.kind, prop.key.span());
let previous = if len <= INLINE_CAPACITY {
if let Some((_, previous)) = inline.iter_mut().find(|(key, _)| *key == name) {
Some(std::mem::replace(previous, value))
} else {
inline.push((name, value));
None
}
} else {
map.insert(name, value)
};
if let Some((prev_kind, prev_span)) = previous
&& (prev_kind == PropertyKind::Init
|| prop.kind == PropertyKind::Init
|| prev_kind == prop.kind)