mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-08 22:21:45 +00:00
Add reserved keyword node ID lint rule
Warn when DOT reserved keywords (graph, digraph, subgraph, node, edge, strict, if) are used as node IDs since they cause silent routing failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ea89a62768
commit
1245106cea
1 changed files with 92 additions and 1 deletions
|
|
@ -4,7 +4,7 @@ use crate::graph::{AttrValue, Graph};
|
|||
|
||||
use super::{Diagnostic, LintRule, Severity};
|
||||
|
||||
/// Returns all 15 built-in lint rules.
|
||||
/// Returns all 16 built-in lint rules.
|
||||
#[must_use]
|
||||
pub fn built_in_rules() -> Vec<Box<dyn LintRule>> {
|
||||
vec![
|
||||
|
|
@ -23,6 +23,7 @@ pub fn built_in_rules() -> Vec<Box<dyn LintRule>> {
|
|||
Box::new(PromptOnLlmNodesRule),
|
||||
Box::new(FreeformEdgeCountRule),
|
||||
Box::new(DirectionValidRule),
|
||||
Box::new(ReservedKeywordNodeIdRule),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -703,6 +704,42 @@ impl LintRule for DirectionValidRule {
|
|||
}
|
||||
}
|
||||
|
||||
// --- Rule 16: reserved_keyword_node_id (WARNING) ---
|
||||
|
||||
struct ReservedKeywordNodeIdRule;
|
||||
|
||||
const DOT_RESERVED_KEYWORDS: &[&str] = &[
|
||||
"graph", "digraph", "subgraph", "node", "edge", "strict", "if",
|
||||
];
|
||||
|
||||
impl LintRule for ReservedKeywordNodeIdRule {
|
||||
fn name(&self) -> &'static str {
|
||||
"reserved_keyword_node_id"
|
||||
}
|
||||
|
||||
fn apply(&self, graph: &Graph) -> Vec<Diagnostic> {
|
||||
graph
|
||||
.nodes
|
||||
.values()
|
||||
.filter(|node| DOT_RESERVED_KEYWORDS.contains(&node.id.to_lowercase().as_str()))
|
||||
.map(|node| Diagnostic {
|
||||
rule: self.name().to_string(),
|
||||
severity: Severity::Warning,
|
||||
message: format!(
|
||||
"Node ID '{}' is a DOT reserved keyword and may cause parsing failures",
|
||||
node.id
|
||||
),
|
||||
node_id: Some(node.id.clone()),
|
||||
edge: None,
|
||||
fix: Some(format!(
|
||||
"Rename '{}' to '{}_step' or another non-reserved ID",
|
||||
node.id, node.id.to_lowercase()
|
||||
)),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -2164,4 +2201,58 @@ mod tests {
|
|||
let d = rule.apply(&g);
|
||||
assert!(d.is_empty());
|
||||
}
|
||||
|
||||
// --- reserved_keyword_node_id tests ---
|
||||
|
||||
#[test]
|
||||
fn reserved_keyword_node_id_warns_on_keyword() {
|
||||
let mut g = minimal_graph();
|
||||
g.nodes
|
||||
.insert("graph".to_string(), Node::new("graph"));
|
||||
g.edges.push(Edge::new("start", "graph"));
|
||||
g.edges.push(Edge::new("graph", "exit"));
|
||||
let rule = ReservedKeywordNodeIdRule;
|
||||
let d = rule.apply(&g);
|
||||
assert_eq!(d.len(), 1);
|
||||
assert_eq!(d[0].severity, Severity::Warning);
|
||||
assert_eq!(d[0].node_id.as_deref(), Some("graph"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserved_keyword_node_id_case_insensitive() {
|
||||
let mut g = minimal_graph();
|
||||
g.nodes.insert("Node".to_string(), Node::new("Node"));
|
||||
g.nodes.insert("EDGE".to_string(), Node::new("EDGE"));
|
||||
g.edges.push(Edge::new("start", "Node"));
|
||||
g.edges.push(Edge::new("Node", "EDGE"));
|
||||
g.edges.push(Edge::new("EDGE", "exit"));
|
||||
let rule = ReservedKeywordNodeIdRule;
|
||||
let d = rule.apply(&g);
|
||||
assert_eq!(d.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserved_keyword_node_id_normal_id_no_warning() {
|
||||
let g = minimal_graph();
|
||||
let rule = ReservedKeywordNodeIdRule;
|
||||
let d = rule.apply(&g);
|
||||
assert!(d.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserved_keyword_node_id_multiple_keywords() {
|
||||
let mut g = minimal_graph();
|
||||
g.nodes
|
||||
.insert("strict".to_string(), Node::new("strict"));
|
||||
g.nodes
|
||||
.insert("digraph".to_string(), Node::new("digraph"));
|
||||
g.nodes.insert("if".to_string(), Node::new("if"));
|
||||
g.edges.push(Edge::new("start", "strict"));
|
||||
g.edges.push(Edge::new("strict", "digraph"));
|
||||
g.edges.push(Edge::new("digraph", "if"));
|
||||
g.edges.push(Edge::new("if", "exit"));
|
||||
let rule = ReservedKeywordNodeIdRule;
|
||||
let d = rule.apply(&g);
|
||||
assert_eq!(d.len(), 3);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue