Skip to main content

sshconfig_lint/
model.rs

1/// Span tracks where something came from in the source.
2#[derive(Debug, Clone, PartialEq, Eq)]
3pub struct Span {
4    /// 1-based line number.
5    pub line: usize,
6    /// Optional file path (for includes).
7    pub file: Option<String>,
8}
9
10impl Span {
11    pub fn new(line: usize) -> Self {
12        Self { line, file: None }
13    }
14
15    pub fn with_file(line: usize, file: impl Into<String>) -> Self {
16        Self {
17            line,
18            file: Some(file.into()),
19        }
20    }
21}
22
23/// A single lexed line.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum LineKind {
26    Empty,
27    Comment(String),
28    Directive { key: String, value: String },
29}
30
31/// A lexed line with its span.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct Line {
34    pub kind: LineKind,
35    pub span: Span,
36}
37
38/// Parsed items that form the config AST.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum Item {
41    /// A comment line.
42    Comment { text: String, span: Span },
43    /// A standalone directive (at root level or inside a block).
44    Directive {
45        key: String,
46        value: String,
47        span: Span,
48    },
49    /// A Host block with patterns and child directives.
50    HostBlock {
51        patterns: Vec<String>,
52        span: Span,
53        items: Vec<Item>,
54    },
55    /// A Match block with its criteria and child directives.
56    MatchBlock {
57        criteria: String,
58        span: Span,
59        items: Vec<Item>,
60    },
61    /// An Include directive with one or more patterns.
62    Include { patterns: Vec<String>, span: Span },
63}
64
65/// The full parsed config.
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct Config {
68    pub items: Vec<Item>,
69}
70
71/// Severity level for a lint finding.
72#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
73pub enum Severity {
74    Info,
75    Warning,
76    Error,
77}
78
79impl std::fmt::Display for Severity {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        match self {
82            Severity::Info => write!(f, "info"),
83            Severity::Warning => write!(f, "warning"),
84            Severity::Error => write!(f, "error"),
85        }
86    }
87}
88
89/// A single lint finding/diagnostic.
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct Finding {
92    pub severity: Severity,
93    pub code: &'static str,
94    pub message: String,
95    pub hint: Option<String>,
96    pub span: Span,
97    /// Rule name like "duplicate-host".
98    pub rule: String,
99}
100
101impl Finding {
102    pub fn new(
103        severity: Severity,
104        rule: impl Into<String>,
105        code: &'static str,
106        message: impl Into<String>,
107        span: Span,
108    ) -> Self {
109        Self {
110            severity,
111            code,
112            message: message.into(),
113            hint: None,
114            span,
115            rule: rule.into(),
116        }
117    }
118
119    pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
120        self.hint = Some(hint.into());
121        self
122    }
123}