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 find_definition_at_position(&self, pos: usize, ast: &Document) -> Option<SymbolInfo> {
231 for item in &ast.items {
233 match item {
234 DocumentItem::Table(table) => {
235 if pos >= table.name.span.start && pos <= table.name.span.end {
237 return Some(SymbolInfo {
239 name: table.name.name.clone(),
240 kind: SymbolKind::Table,
241 span: table.name.span.clone(),
242 });
243 }
244
245 for table_item in &table.items {
247 if let TableItem::Column(column) = table_item {
248 if pos >= column.name.span.start && pos <= column.name.span.end {
250 return Some(SymbolInfo {
251 name: column.name.name.clone(),
252 kind: SymbolKind::Column,
253 span: column.name.span.clone(),
254 });
255 }
256
257 if pos >= column.col_type_span.start && pos <= column.col_type_span.end {
259 if let Some(enum_symbol) = self.enums.get(&column.col_type) {
261 return Some(enum_symbol.clone());
262 }
263 }
264
265 for setting in &column.settings {
267 if let ColumnSetting::Ref(inline_ref) = setting {
268 if pos >= inline_ref.target_table.span.start
270 && pos <= inline_ref.target_table.span.end
271 {
272 if let Some(table_symbol) =
273 self.tables.get(&inline_ref.target_table.name)
274 {
275 return Some(table_symbol.clone());
276 }
277 }
278
279 if pos >= inline_ref.target_column.span.start
281 && pos <= inline_ref.target_column.span.end
282 {
283 if let Some(columns) =
285 self.columns.get(&inline_ref.target_table.name)
286 {
287 for col in columns {
288 if col.name == inline_ref.target_column.name {
289 return Some(col.clone());
290 }
291 }
292 }
293 }
294 }
295 }
296 }
297 }
298 }
299 DocumentItem::Enum(enum_def) => {
300 if pos >= enum_def.name.span.start && pos <= enum_def.name.span.end {
302 return Some(SymbolInfo {
303 name: enum_def.name.name.clone(),
304 kind: SymbolKind::Enum,
305 span: enum_def.name.span.clone(),
306 });
307 }
308
309 for member in &enum_def.members {
311 if pos >= member.name.span.start && pos <= member.name.span.end {
312 return Some(SymbolInfo {
313 name: member.name.name.clone(),
314 kind: SymbolKind::EnumMember,
315 span: member.name.span.clone(),
316 });
317 }
318 }
319 }
320 DocumentItem::Ref(ref_def) => {
321 if pos >= ref_def.from_table.span.start && pos <= ref_def.from_table.span.end
323 {
324 if let Some(table_symbol) = self.tables.get(&ref_def.from_table.name) {
325 return Some(table_symbol.clone());
326 }
327 }
328
329 if pos >= ref_def.to_table.span.start && pos <= ref_def.to_table.span.end {
331 if let Some(table_symbol) = self.tables.get(&ref_def.to_table.name) {
332 return Some(table_symbol.clone());
333 }
334 }
335
336 for from_col in &ref_def.from_columns {
338 if pos >= from_col.span.start && pos <= from_col.span.end {
339 if let Some(columns) = self.columns.get(&ref_def.from_table.name) {
341 for col in columns {
342 if col.name == from_col.name {
343 return Some(col.clone());
344 }
345 }
346 }
347 }
348 }
349
350 for to_col in &ref_def.to_columns {
352 if pos >= to_col.span.start && pos <= to_col.span.end {
353 if let Some(columns) = self.columns.get(&ref_def.to_table.name) {
355 for col in columns {
356 if col.name == to_col.name {
357 return Some(col.clone());
358 }
359 }
360 }
361 }
362 }
363 }
364 DocumentItem::Project(_) => {
365 }
367 }
368 }
369
370 None
371 }
372
373 pub fn to_diagnostics(&self, content: &str) -> Vec<Diagnostic> {
374 self.errors
375 .iter()
376 .map(|err| {
377 let range = span_to_range(&err.span, content);
378 Diagnostic {
379 range,
380 severity: Some(DiagnosticSeverity::ERROR),
381 code: None,
382 code_description: None,
383 source: Some("dbml-lsp".to_string()),
384 message: err.message.clone(),
385 related_information: None,
386 tags: None,
387 data: None,
388 }
389 })
390 .collect()
391 }
392}
393
394fn span_to_range(span: &Span, content: &str) -> Range {
395 let start_pos = offset_to_position(span.start, content);
396 let end_pos = offset_to_position(span.end, content);
397 Range::new(start_pos, end_pos)
398}
399
400fn offset_to_position(offset: usize, content: &str) -> Position {
401 let mut line = 0;
402 let mut col = 0;
403
404 for (i, ch) in content.chars().enumerate() {
405 if i >= offset {
406 break;
407 }
408 if ch == '\n' {
409 line += 1;
410 col = 0;
411 } else {
412 col += 1;
413 }
414 }
415
416 Position::new(line, col)
417}