perf(linter/eslint/no-unassigned-vars): batch by declaration (#28358)

Share declaration context checks across candidate bindings while
preserving per-binding read/write decisions. Reduce fixture callbacks
from 37,499 to 31,363.


---

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Cam McHenryandCopilot authored and GitHub committed 2026-10-28 22:28:47 -04:00
1 parent 1108352314
commit dbd8a0b7f1
4 files changed
+53 -25

No files matched your search

+1 -1
View File
@@ -1095,7 +1095,7 @@ impl RuleRunner for crate::rules::eslint::no_throw_literal::NoThrowLiteral {
impl RuleRunner for crate::rules::eslint::no_unassigned_vars::NoUnassignedVars {
const NODE_TYPES: Option<&AstTypesBitset> =
Some(&AstTypesBitset::from_types(&[AstType::VariableDeclarator]));
Some(&AstTypesBitset::from_types(&[AstType::VariableDeclaration]));
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
}
@@ -65,27 +65,30 @@ declare_oxc_lint!(
impl Rule for NoUnassignedVars {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::VariableDeclarator(declarator) = node.kind() else {
let AstKind::VariableDeclaration(declaration) = node.kind() else {
return;
};
if declarator.init.is_some() {
if declaration.kind.is_const() || declaration.declare {
return;
}
let parent_node = ctx.nodes().parent_node(node.id());
let AstKind::VariableDeclaration(parent) = parent_node.kind() else {
return;
};
if parent.kind.is_const() || parent.declare {
let mut identifiers = declaration
.declarations
.iter()
.filter_map(|declarator| match (&declarator.id, &declarator.init) {
(BindingPattern::BindingIdentifier(ident), None) => Some(ident),
_ => None,
})
.peekable();
if identifiers.peek().is_none() {
return;
}
let grand_parent = ctx.nodes().parent_node(parent_node.id());
if matches!(
grand_parent.kind(),
ctx.nodes().parent_kind(node.id()),
AstKind::ForStatement(_) | AstKind::ForInStatement(_) | AstKind::ForOfStatement(_)
) {
return;
}
if ctx.nodes().ancestors(node.id()).skip(1).any(|ancestor| {
if ctx.nodes().ancestors(node.id()).any(|ancestor| {
matches!(
ancestor.kind(),
AstKind::TSExternalModuleDeclaration(_)
@@ -95,22 +98,21 @@ impl Rule for NoUnassignedVars {
}) {
return;
}
let BindingPattern::BindingIdentifier(ident) = &declarator.id else {
return;
};
let symbol_id = ident.symbol_id();
let mut has_read = false;
for reference in ctx.symbol_references(symbol_id) {
if reference.is_write() {
return;
for ident in identifiers {
let mut has_read = false;
for reference in ctx.symbol_references(ident.symbol_id()) {
if reference.is_write() {
has_read = false;
break;
}
if reference.is_read() {
has_read = true;
}
}
if reference.is_read() {
has_read = true;
if has_read {
ctx.diagnostic(no_unassigned_vars_diagnostic(ident.span, ident.name.as_str()));
}
}
if has_read {
ctx.diagnostic(no_unassigned_vars_diagnostic(ident.span, ident.name.as_str()));
}
}
fn should_run(&self, ctx: &crate::context::ContextHost) -> bool {
@@ -150,6 +152,8 @@ fn test() {
}
",
"for (let p of pathToRemove) { p.remove() }",
"for (let a, b; check(a, b);) { break; }",
"declare let a, b; log(a, b);",
];
let fail = vec![
@@ -162,6 +166,9 @@ fn test() {
"let config; function init() { return config?.enabled; }",
"let x: number; log(x);",
"let x: number | undefined; log(x);",
"let assigned, unassigned; assigned = 1; log(assigned, unassigned);",
"let a, b; log(a, b); a = 1;",
"let initialized = 1, unassigned; log(initialized, unassigned);",
"const foo = (two: string): void => { let one: string | undefined; if (one === two) {} }",
"
declare module 'module' {
@@ -72,6 +72,27 @@ source: crates/oxc_linter/src/tester.rs
╰────
help: Variable declared without assignment. Either assign a value or remove the declaration.
⚠ eslint(no-unassigned-vars): 'unassigned' is always 'undefined' because it's never assigned.
╭─[no_unassigned_vars.tsx:1:15]
1 │ let assigned, unassigned; assigned = 1; log(assigned, unassigned);
· ──────────
╰────
help: Variable declared without assignment. Either assign a value or remove the declaration.
⚠ eslint(no-unassigned-vars): 'b' is always 'undefined' because it's never assigned.
╭─[no_unassigned_vars.tsx:1:8]
1 │ let a, b; log(a, b); a = 1;
· ─
╰────
help: Variable declared without assignment. Either assign a value or remove the declaration.
⚠ eslint(no-unassigned-vars): 'unassigned' is always 'undefined' because it's never assigned.
╭─[no_unassigned_vars.tsx:1:22]
1 │ let initialized = 1, unassigned; log(initialized, unassigned);
· ──────────
╰────
help: Variable declared without assignment. Either assign a value or remove the declaration.
⚠ eslint(no-unassigned-vars): 'one' is always 'undefined' because it's never assigned.
╭─[no_unassigned_vars.tsx:1:42]
1 │ const foo = (two: string): void => { let one: string | undefined; if (one === two) {} }
@@ -121,7 +121,7 @@ eslint/no-template-curly-in-string | 62460
eslint/no-ternary | 4722
eslint/no-this-before-super | 4
eslint/no-throw-literal | 224
eslint/no-unassigned-vars | 37499
eslint/no-unassigned-vars | 31363
eslint/no-undef | 7
eslint/no-undefined | 282877
eslint/no-underscore-dangle | 200305