use crate::linter::diagnostic::{Diagnostic, Severity, ViolationData};
use crate::linter::rules::{Rule, RuleContext};
use crate::semantic::PackageOrigin;
pub struct UndefinedSymbol;
impl Rule for UndefinedSymbol {
fn id(&self) -> &'static str {
"undefined-symbol"
}
fn default_severity(&self) -> Severity {
Severity::Warning
}
fn default_enabled(&self) -> bool {
true
}
fn run(&self, ctx: &RuleContext<'_>) -> Vec<Diagnostic> {
match ctx.resolution {
Some(resolution) => ctx
.model
.idents()
.iter()
.filter(|ident| ctx.model.resolve_local(ident).is_none())
.filter(|ident| resolution.unresolved.contains(ident.name.as_str()))
.map(|ident| undefined(&ident.name, ident.range))
.collect(),
None => self.run_standalone(ctx),
}
}
}
impl UndefinedSymbol {
fn run_standalone(&self, ctx: &RuleContext<'_>) -> Vec<Diagnostic> {
let mut out = Vec::new();
let loaded = ctx.model.loaded_packages();
if loaded.iter().any(|p| !ctx.symbols.package_indexed(&p.name)) {
return out;
}
if ctx.project.is_some_and(|p| p.resolution_incomplete) {
return out;
}
for ident in ctx.model.idents() {
if ctx.model.resolve_local(ident).is_some() {
continue;
}
if ctx.project.is_some_and(|p| p.resolves(&ident.name)) {
continue;
}
if !matches!(
ctx.symbols.origin(&ident.name, loaded),
PackageOrigin::Unknown
) {
continue;
}
out.push(undefined(&ident.name, ident.range));
}
out
}
}
fn undefined(name: &str, range: rowan::TextRange) -> Diagnostic {
Diagnostic {
rule: "undefined-symbol",
severity: Severity::Warning,
path: Default::default(),
range,
message: ViolationData::new(
"undefined-symbol",
format!("no in-scope binding or attached package exports `{name}`"),
),
fix: None,
}
}