dbml_language_server/analysis/
semantic.rs1use 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>>, 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 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 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 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 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 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 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 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 for symbol in self.tables.values() {
204 if symbol.span.contains(&pos) {
205 return Some(symbol);
206 }
207 }
208
209 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 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}