dbml_language_server/analysis/
semantic.rs

1// src/analysis/semantic.rs
2use crate::ast::*;
3use lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone)]
7#[allow(dead_code)]
8pub struct SymbolInfo {
9    pub name: String,
10    pub kind: SymbolKind,
11    pub span: Span,
12}
13
14#[derive(Debug, Clone, PartialEq)]
15#[allow(dead_code)]
16pub enum SymbolKind {
17    Table,
18    Column,
19    Enum,
20    EnumMember,
21}
22
23#[derive(Debug, Clone)]
24pub struct SemanticError {
25    pub message: String,
26    pub span: Span,
27}
28
29#[derive(Debug)]
30pub struct SemanticModel {
31    pub tables: HashMap<String, SymbolInfo>,
32    pub columns: HashMap<String, Vec<SymbolInfo>>, // table_name -> columns
33    pub enums: HashMap<String, SymbolInfo>,
34    pub errors: Vec<SemanticError>,
35}
36
37impl SemanticModel {
38    pub fn new() -> Self {
39        Self {
40            tables: HashMap::new(),
41            columns: HashMap::new(),
42            enums: HashMap::new(),
43            errors: Vec::new(),
44        }
45    }
46
47    pub fn analyze(document: &Document) -> Self {
48        let mut model = Self::new();
49
50        // Pass 1: Collect all definitions
51        for item in &document.items {
52            match item {
53                DocumentItem::Table(table) => {
54                    model.analyze_table(table);
55                }
56                DocumentItem::Enum(enum_def) => {
57                    model.analyze_enum(enum_def);
58                }
59                DocumentItem::Ref(ref_def) => {
60                    model.validate_ref(ref_def);
61                }
62                DocumentItem::Project(_) => {}
63            }
64        }
65
66        model
67    }
68
69    fn analyze_table(&mut self, table: &Table) {
70        let table_name = table.name.name.clone();
71
72        // Check for duplicate table
73        if self.tables.contains_key(&table_name) {
74            self.errors.push(SemanticError {
75                message: format!("Duplicate table definition: '{}'", table_name),
76                span: table.name.span.clone(),
77            });
78        } else {
79            self.tables.insert(
80                table_name.clone(),
81                SymbolInfo {
82                    name: table_name.clone(),
83                    kind: SymbolKind::Table,
84                    span: table.name.span.clone(),
85                },
86            );
87        }
88
89        // Analyze columns
90        let mut column_names = Vec::new();
91        for item in &table.items {
92            if let TableItem::Column(col) = item {
93                let col_name = col.name.name.clone();
94
95                // Check for duplicate column
96                if column_names.iter().any(|c: &SymbolInfo| c.name == col_name) {
97                    self.errors.push(SemanticError {
98                        message: format!(
99                            "Duplicate column '{}' in table '{}'",
100                            col_name, table_name
101                        ),
102                        span: col.name.span.clone(),
103                    });
104                } else {
105                    column_names.push(SymbolInfo {
106                        name: col_name.clone(),
107                        kind: SymbolKind::Column,
108                        span: col.name.span.clone(),
109                    });
110                }
111
112                // Validate column settings
113                for setting in &col.settings {
114                    if let ColumnSetting::Ref(inline_ref) = setting {
115                        self.validate_inline_ref(inline_ref, &table_name);
116                    }
117                }
118            }
119        }
120
121        self.columns.insert(table_name, column_names);
122    }
123
124    fn analyze_enum(&mut self, enum_def: &Enum) {
125        let enum_name = enum_def.name.name.clone();
126
127        if self.enums.contains_key(&enum_name) {
128            self.errors.push(SemanticError {
129                message: format!("Duplicate enum definition: '{}'", enum_name),
130                span: enum_def.name.span.clone(),
131            });
132        } else {
133            self.enums.insert(
134                enum_name.clone(),
135                SymbolInfo {
136                    name: enum_name,
137                    kind: SymbolKind::Enum,
138                    span: enum_def.name.span.clone(),
139                },
140            );
141        }
142    }
143
144    fn validate_ref(&mut self, ref_def: &Ref) {
145        let from_table = &ref_def.from_table.name;
146        let to_table = &ref_def.to_table.name;
147
148        // Check if tables exist
149        if !self.tables.contains_key(from_table) {
150            self.errors.push(SemanticError {
151                message: format!("Table '{}' not found", from_table),
152                span: ref_def.from_table.span.clone(),
153            });
154        }
155
156        if !self.tables.contains_key(to_table) {
157            self.errors.push(SemanticError {
158                message: format!("Table '{}' not found", to_table),
159                span: ref_def.to_table.span.clone(),
160            });
161        }
162
163        // Validate columns exist in their respective tables
164        for col in &ref_def.from_columns {
165            if let Some(columns) = self.columns.get(from_table) {
166                if !columns.iter().any(|c| c.name == col.name) {
167                    self.errors.push(SemanticError {
168                        message: format!(
169                            "Column '{}' not found in table '{}'",
170                            col.name, from_table
171                        ),
172                        span: col.span.clone(),
173                    });
174                }
175            }
176        }
177
178        for col in &ref_def.to_columns {
179            if let Some(columns) = self.columns.get(to_table) {
180                if !columns.iter().any(|c| c.name == col.name) {
181                    self.errors.push(SemanticError {
182                        message: format!("Column '{}' not found in table '{}'", col.name, to_table),
183                        span: col.span.clone(),
184                    });
185                }
186            }
187        }
188    }
189
190    fn validate_inline_ref(&mut self, inline_ref: &InlineRef, _current_table: &str) {
191        let target_table = &inline_ref.target_table.name;
192
193        if !self.tables.contains_key(target_table) {
194            self.errors.push(SemanticError {
195                message: format!("Referenced table '{}' not found", target_table),
196                span: inline_ref.target_table.span.clone(),
197            });
198        }
199    }
200
201    pub fn find_symbol_at_position(&self, pos: usize) -> Option<&SymbolInfo> {
202        // Search in tables
203        for symbol in self.tables.values() {
204            if symbol.span.contains(&pos) {
205                return Some(symbol);
206            }
207        }
208
209        // Search in columns
210        for columns in self.columns.values() {
211            for symbol in columns {
212                if symbol.span.contains(&pos) {
213                    return Some(symbol);
214                }
215            }
216        }
217
218        // Search in enums
219        for symbol in self.enums.values() {
220            if symbol.span.contains(&pos) {
221                return Some(symbol);
222            }
223        }
224
225        None
226    }
227
228    pub fn to_diagnostics(&self, content: &str) -> Vec<Diagnostic> {
229        self.errors
230            .iter()
231            .map(|err| {
232                let range = span_to_range(&err.span, content);
233                Diagnostic {
234                    range,
235                    severity: Some(DiagnosticSeverity::ERROR),
236                    code: None,
237                    code_description: None,
238                    source: Some("dbml-lsp".to_string()),
239                    message: err.message.clone(),
240                    related_information: None,
241                    tags: None,
242                    data: None,
243                }
244            })
245            .collect()
246    }
247}
248
249fn span_to_range(span: &Span, content: &str) -> Range {
250    let start_pos = offset_to_position(span.start, content);
251    let end_pos = offset_to_position(span.end, content);
252    Range::new(start_pos, end_pos)
253}
254
255fn offset_to_position(offset: usize, content: &str) -> Position {
256    let mut line = 0;
257    let mut col = 0;
258
259    for (i, ch) in content.chars().enumerate() {
260        if i >= offset {
261            break;
262        }
263        if ch == '\n' {
264            line += 1;
265            col = 0;
266        } else {
267            col += 1;
268        }
269    }
270
271    Position::new(line, col)
272}