dbml_language_server/
formatter.rs1use crate::ast::*;
3
4#[derive(Debug, Clone)]
6pub struct FormattingOptions {
7 pub tab_size: u32,
9 pub insert_spaces: bool,
11 pub trim_trailing_whitespace: bool,
13 pub insert_final_newline: bool,
15 pub trim_final_newlines: bool,
17}
18
19impl Default for FormattingOptions {
20 fn default() -> Self {
21 Self {
22 tab_size: 2,
23 insert_spaces: true,
24 trim_trailing_whitespace: true,
25 insert_final_newline: true,
26 trim_final_newlines: true,
27 }
28 }
29}
30
31impl FormattingOptions {
32 pub fn indent(&self, level: usize) -> String {
34 let single_indent = if self.insert_spaces {
35 " ".repeat(self.tab_size as usize)
36 } else {
37 "\t".to_string()
38 };
39 single_indent.repeat(level)
40 }
41}
42
43pub fn format_document(ast: &Document, options: &FormattingOptions) -> String {
45 let mut output = String::new();
46 let mut first = true;
47
48 for item in &ast.items {
49 if !first {
50 output.push_str("\n\n");
51 }
52 first = false;
53
54 match item {
55 DocumentItem::Table(table) => {
56 output.push_str(&format_table(table, options));
57 }
58 DocumentItem::Enum(enum_def) => {
59 output.push_str(&format_enum(enum_def, options));
60 }
61 DocumentItem::Ref(ref_def) => {
62 output.push_str(&format_ref(ref_def));
63 }
64 DocumentItem::Project(project) => {
65 output.push_str(&format_project(project, options));
66 }
67 }
68 }
69
70 if options.trim_final_newlines {
72 output = output.trim_end().to_string();
73 }
74
75 if options.insert_final_newline && !output.ends_with('\n') {
76 output.push('\n');
77 }
78
79 if options.trim_trailing_whitespace {
81 output = output
82 .lines()
83 .map(|line| line.trim_end())
84 .collect::<Vec<_>>()
85 .join("\n");
86
87 if options.insert_final_newline && !output.ends_with('\n') {
89 output.push('\n');
90 }
91 }
92
93 output
94}
95
96fn format_table(table: &Table, options: &FormattingOptions) -> String {
97 let mut output = String::new();
98
99 output.push_str("Table ");
101 if let Some(schema) = &table.schema {
102 output.push_str(&schema.name);
103 output.push('.');
104 }
105 output.push_str(&table.name.name);
106
107 if let Some(alias) = &table.alias {
108 output.push_str(" as ");
109 output.push_str(&alias.name);
110 }
111
112 output.push_str(" {\n");
113
114 for item in &table.items {
116 match item {
117 TableItem::Column(column) => {
118 output.push_str(&format_column(column, 1, options));
119 }
120 TableItem::Indexes(indexes_block) => {
121 output.push_str("\n");
122 output.push_str(&options.indent(1));
123 output.push_str("indexes {\n");
124 for index in &indexes_block.indexes {
125 output.push_str(&format_index(index, 2, options));
126 }
127 output.push_str(&options.indent(1));
128 output.push_str("}\n");
129 }
130 TableItem::Note(note) => {
131 output.push_str(&options.indent(1));
132 output.push_str("Note: ");
133 output.push_str(&format_string_literal(note));
134 output.push('\n');
135 }
136 }
137 }
138
139 if !table.settings.is_empty() {
141 output.push('\n');
142 for setting in &table.settings {
143 output.push_str(&options.indent(1));
144 output.push_str(&format!("{}: {}\n", setting.key, setting.value));
145 }
146 }
147
148 output.push_str("}\n");
149 output
150}
151
152fn format_column(column: &Column, indent_level: usize, options: &FormattingOptions) -> String {
153 let mut output = String::new();
154
155 output.push_str(&options.indent(indent_level));
156 output.push_str(&column.name.name);
157 output.push(' ');
158 output.push_str(&column.col_type);
159
160 if !column.settings.is_empty() {
161 output.push_str(" [");
162 let settings: Vec<String> = column
163 .settings
164 .iter()
165 .map(|s| format_column_setting(s))
166 .collect();
167 output.push_str(&settings.join(", "));
168 output.push(']');
169 }
170
171 output.push('\n');
172 output
173}
174
175fn format_column_setting(setting: &ColumnSetting) -> String {
176 match setting {
177 ColumnSetting::PrimaryKey => "pk".to_string(),
178 ColumnSetting::NotNull => "not null".to_string(),
179 ColumnSetting::Null => "null".to_string(),
180 ColumnSetting::Unique => "unique".to_string(),
181 ColumnSetting::Increment => "increment".to_string(),
182 ColumnSetting::Default(val) => format!("default: {}", format_default_value(val)),
183 ColumnSetting::Note(note) => format!("note: {}", format_string_literal(note)),
184 ColumnSetting::Ref(inline_ref) => {
185 let rel = format_relationship(&inline_ref.relationship);
186 format!(
187 "ref: {} {}.{}",
188 rel, inline_ref.target_table.name, inline_ref.target_column.name
189 )
190 }
191 }
192}
193
194fn format_default_value(val: &DefaultValue) -> String {
195 match val {
196 DefaultValue::String(s) => format!("'{}'", s.replace('\'', "\\'")),
197 DefaultValue::Number(n) => n.clone(),
198 DefaultValue::Boolean(b) => b.to_string(),
199 DefaultValue::Expression(e) => format!("`{}`", e),
200 }
201}
202
203fn format_string_literal(lit: &StringLiteral) -> String {
204 format!("'{}'", lit.value.replace('\'', "\\'"))
206}
207
208fn format_relationship(rel: &RelationshipType) -> String {
209 match rel {
210 RelationshipType::OneToOne => "-".to_string(),
211 RelationshipType::OneToMany => "<".to_string(),
212 RelationshipType::ManyToOne => ">".to_string(),
213 RelationshipType::ManyToMany => "<>".to_string(),
214 }
215}
216
217fn format_index(index: &Index, indent_level: usize, options: &FormattingOptions) -> String {
218 let mut output = String::new();
219
220 output.push_str(&options.indent(indent_level));
221 output.push('(');
222
223 let columns: Vec<String> = index
224 .columns
225 .iter()
226 .map(|col| match col {
227 IndexColumn::Simple(ident) => ident.name.clone(),
228 IndexColumn::Expression(expr) => format!("`{}`", expr),
229 })
230 .collect();
231
232 output.push_str(&columns.join(", "));
233 output.push(')');
234
235 if !index.settings.is_empty() {
236 output.push_str(" [");
237 let settings: Vec<String> = index
238 .settings
239 .iter()
240 .map(|s| format_index_setting(s))
241 .collect();
242 output.push_str(&settings.join(", "));
243 output.push(']');
244 }
245
246 output.push('\n');
247 output
248}
249
250fn format_index_setting(setting: &IndexSetting) -> String {
251 match setting {
252 IndexSetting::PrimaryKey => "pk".to_string(),
253 IndexSetting::Unique => "unique".to_string(),
254 IndexSetting::Name(name) => format!("name: '{}'", name.replace('\'', "\\'")),
255 IndexSetting::Type(idx_type) => format!("type: {}", idx_type),
256 }
257}
258
259fn format_enum(enum_def: &Enum, options: &FormattingOptions) -> String {
260 let mut output = String::new();
261
262 output.push_str("enum ");
263 output.push_str(&enum_def.name.name);
264 output.push_str(" {\n");
265
266 for member in &enum_def.members {
267 output.push_str(&options.indent(1));
268 output.push_str(&member.name.name);
269
270 if let Some(note) = &member.note {
271 output.push_str(" [note: ");
272 output.push_str(&format_string_literal(note));
273 output.push(']');
274 }
275
276 output.push('\n');
277 }
278
279 output.push_str("}\n");
280 output
281}
282
283fn format_ref(ref_def: &Ref) -> String {
284 let mut output = String::new();
285
286 output.push_str("Ref");
287
288 if let Some(name) = &ref_def.name {
289 output.push(' ');
290 output.push_str(&name.name);
291 }
292
293 output.push_str(": ");
294 output.push_str(&ref_def.from_table.name);
295 output.push('.');
296
297 if ref_def.from_columns.len() == 1 {
298 output.push_str(&ref_def.from_columns[0].name);
299 } else {
300 output.push('(');
301 let cols: Vec<String> = ref_def
302 .from_columns
303 .iter()
304 .map(|c| c.name.clone())
305 .collect();
306 output.push_str(&cols.join(", "));
307 output.push(')');
308 }
309
310 output.push(' ');
311 output.push_str(&format_relationship(&ref_def.relationship));
312 output.push(' ');
313
314 output.push_str(&ref_def.to_table.name);
315 output.push('.');
316
317 if ref_def.to_columns.len() == 1 {
318 output.push_str(&ref_def.to_columns[0].name);
319 } else {
320 output.push('(');
321 let cols: Vec<String> = ref_def.to_columns.iter().map(|c| c.name.clone()).collect();
322 output.push_str(&cols.join(", "));
323 output.push(')');
324 }
325
326 let mut settings = Vec::new();
328 if let Some(on_delete) = &ref_def.on_delete {
329 settings.push(format!("delete: {}", format_referential_action(on_delete)));
330 }
331 if let Some(on_update) = &ref_def.on_update {
332 settings.push(format!("update: {}", format_referential_action(on_update)));
333 }
334
335 if !settings.is_empty() {
336 output.push_str(" [");
337 output.push_str(&settings.join(", "));
338 output.push(']');
339 }
340
341 output.push('\n');
342 output
343}
344
345fn format_referential_action(action: &ReferentialAction) -> String {
346 match action {
347 ReferentialAction::Cascade => "cascade".to_string(),
348 ReferentialAction::Restrict => "restrict".to_string(),
349 ReferentialAction::NoAction => "no action".to_string(),
350 ReferentialAction::SetNull => "set null".to_string(),
351 ReferentialAction::SetDefault => "set default".to_string(),
352 }
353}
354
355fn format_project(project: &Project, options: &FormattingOptions) -> String {
356 let mut output = String::new();
357
358 output.push_str("Project");
359
360 if let Some(name) = &project.name {
361 output.push(' ');
362 output.push_str(&name.name);
363 }
364
365 output.push_str(" {\n");
366
367 for setting in &project.settings {
368 match setting {
369 ProjectSetting::DatabaseType(db_type) => {
370 output.push_str(&options.indent(1));
371 output.push_str("database_type: '");
372 output.push_str(&db_type.replace('\'', "\\'"));
373 output.push_str("'\n");
374 }
375 ProjectSetting::Note(note) => {
376 output.push_str(&options.indent(1));
377 output.push_str("Note: ");
378 output.push_str(&format_string_literal(note));
379 output.push('\n');
380 }
381 }
382 }
383
384 output.push_str("}\n");
385 output
386}