1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 08:55:38 +00:00

Analyzer: W0103 also fires on unused state-local variables

Previously the unused-variable warning only ran on globals. Now it
walks \`state.locals\` too, so state-scoped variables that are
declared and never read also emit W0103. Factored out to
\`check_unused_var\` to share the \`_\`-prefix exemption and help text.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
This commit is contained in:
Claude 2026-04-12 11:21:53 +00:00
parent b7c806523d
commit 3f87aa1e30
No known key found for this signature in database
2 changed files with 53 additions and 18 deletions

View file

@ -211,25 +211,15 @@ impl Analyzer {
// Compute max call depths from entry points (state handlers)
self.compute_max_depths(program);
// Check for unused global variables (W0103). Variables whose names
// start with '_' are exempt by convention. State-local variables are
// left out for now to avoid noise during early development.
// Check for unused variables (W0103). Variables whose names
// start with '_' are exempt by convention. Both globals and
// state-local variables are checked.
for var in &program.globals {
if var.name.starts_with('_') {
continue;
}
if !self.used_vars.contains(&var.name) {
self.diagnostics.push(Diagnostic {
level: Level::Warning,
code: ErrorCode::W0103,
message: format!("unused variable '{}'", var.name),
span: var.span,
labels: Vec::<Label>::new(),
help: Some(
"prefix with '_' to silence this warning, or remove the declaration".into(),
),
note: None,
});
self.check_unused_var(var);
}
for state in &program.states {
for var in &state.locals {
self.check_unused_var(var);
}
}
@ -242,6 +232,26 @@ impl Analyzer {
self.used_vars.insert(name.to_string());
}
/// Emit W0103 if `var` is never read anywhere. Variables named
/// with a leading `_` are exempt by convention.
fn check_unused_var(&mut self, var: &VarDecl) {
if var.name.starts_with('_') {
return;
}
if self.used_vars.contains(&var.name) {
return;
}
self.diagnostics.push(Diagnostic {
level: Level::Warning,
code: ErrorCode::W0103,
message: format!("unused variable '{}'", var.name),
span: var.span,
labels: Vec::<Label>::new(),
help: Some("prefix with '_' to silence this warning, or remove the declaration".into()),
note: None,
});
}
/// Recursively walk an expression tree and mark every identifier that
/// appears as an `Expr::Ident` (or as an `Expr::ArrayIndex` base) as
/// "read". Used by the W0103 unused-variable analysis. Also emits

View file

@ -562,6 +562,31 @@ fn analyze_unused_variable_warning() {
);
}
#[test]
fn analyze_unused_state_local_warning() {
// State-local `bonus` is declared but never read — W0103 should fire.
let (prog, diags) = parser::parse(
r#"
game "Test" { mapper: NROM }
state Main {
var bonus: u8 = 0
on frame { wait_frame }
}
start Main
"#,
);
assert!(diags.is_empty(), "parse errors: {diags:?}");
let result = analyze(&prog.unwrap());
assert!(
result
.diagnostics
.iter()
.any(|d| d.code == ErrorCode::W0103 && d.message.contains("bonus")),
"expected W0103 for unused state-local 'bonus', got: {:?}",
result.diagnostics
);
}
#[test]
fn analyze_unused_variable_no_warning_when_read() {
// `counter` is both written and read (in the `if` condition),