use crate::linter::{Diagnostic, LintContext, Rule};
use crate::parser::CircuitElement;
pub struct MalformedElementRule;
type ElementSummary<'a> = (&'a str, Option<&'a str>, &'a str);
fn summarize(element: &CircuitElement) -> Option<ElementSummary<'_>> {
Some(match element {
CircuitElement::Component(c) => (
"source_component",
c.component_id.as_deref(),
"source_component_id",
),
CircuitElement::Net(n) => ("source_net", n.net_id.as_deref(), "source_net_id"),
CircuitElement::Port(p) => ("source_port", p.port_id.as_deref(), "source_port_id"),
CircuitElement::Pad(p) => ("source_pad", p.pad_id.as_deref(), "source_pad_id"),
CircuitElement::PcbPort(p) => ("pcb_port", p.pcb_port_id.as_deref(), "pcb_port_id"),
CircuitElement::Trace(t) => ("source_trace", t.trace_id.as_deref(), "source_trace_id"),
CircuitElement::Unknown => return None,
})
}
fn missing_identifier(index: usize, kind: &str, expected: &str) -> Diagnostic {
Diagnostic {
rule: "malformed_element".to_string(),
message: format!(
"element #{index} ({kind}) has no identifier: \
its connections cannot be verified"
),
help: Some(format!(
"add a `{expected}` field — canonical circuit-json always \
carries it, so this file deviates from the format"
)),
..Default::default()
}
}
fn empty_trace(index: usize) -> Diagnostic {
Diagnostic {
rule: "malformed_element".to_string(),
message: format!("element #{index} (source_trace) connects no port and no net"),
help: Some(
"fill `connected_source_port_ids` or `connected_source_net_ids`, or delete the trace"
.to_string(),
),
..Default::default()
}
}
fn defects(index: usize, element: &CircuitElement) -> Vec<Diagnostic> {
let mut found = Vec::new();
if let Some((kind, None, expected)) = summarize(element) {
found.push(missing_identifier(index, kind, expected));
}
if let CircuitElement::Trace(t) = element {
if t.connected_source_port_ids.is_empty() && t.connected_source_net_ids.is_empty() {
found.push(empty_trace(index));
}
}
found
}
impl Rule for MalformedElementRule {
fn name(&self) -> &'static str {
"malformed_element"
}
fn check(&self, ctx: &LintContext<'_>) -> Vec<Diagnostic> {
ctx.circuit
.elements
.iter()
.enumerate()
.flat_map(|(index, element)| defects(index, element))
.collect()
}
}