1use crate::ast::{
15 ComputedKind, Declaration, Expr, FieldAttribute, FieldModifier, FieldType, Literal,
16 ModelAttribute, ReferentialAction, Schema, StorageStrategy, TypeDecl,
17};
18
19pub fn format_schema(schema: &Schema, source: &str) -> String {
28 let mut parts: Vec<(String, usize)> = Vec::new();
29
30 for decl in &schema.declarations {
31 let block = match decl {
32 Declaration::Datasource(ds) => {
33 let max_key = ds
34 .fields
35 .iter()
36 .map(|f| f.name.value.len())
37 .max()
38 .unwrap_or(0);
39 let mut lines = vec![format!("datasource {} {{", ds.name.value)];
40 for (idx, field) in ds.fields.iter().enumerate() {
41 if idx > 0 {
43 let prev = &ds.fields[idx - 1];
44 push_gap_content(source, prev.span.end, field.span.start, &mut lines, " ");
45 }
46 let padding = max_key - field.name.value.len() + 1;
47 let mut line = format!(
48 " {}{}= {}",
49 field.name.value,
50 " ".repeat(padding),
51 format_expr(&field.value)
52 );
53 if let Some(c) = trailing_inline_comment(source, field.span.end) {
54 line.push_str(" ");
55 line.push_str(&c);
56 }
57 lines.push(line);
58 }
59 lines.push("}".to_string());
60 lines.join("\n")
61 }
62
63 Declaration::Generator(gen) => {
64 let max_key = gen
65 .fields
66 .iter()
67 .map(|f| f.name.value.len())
68 .max()
69 .unwrap_or(0);
70 let mut lines = vec![format!("generator {} {{", gen.name.value)];
71 for (idx, field) in gen.fields.iter().enumerate() {
72 if idx > 0 {
73 let prev = &gen.fields[idx - 1];
74 push_gap_content(source, prev.span.end, field.span.start, &mut lines, " ");
75 }
76 let padding = max_key - field.name.value.len() + 1;
77 let mut line = format!(
78 " {}{}= {}",
79 field.name.value,
80 " ".repeat(padding),
81 format_expr(&field.value)
82 );
83 if let Some(c) = trailing_inline_comment(source, field.span.end) {
84 line.push_str(" ");
85 line.push_str(&c);
86 }
87 lines.push(line);
88 }
89 lines.push("}".to_string());
90 lines.join("\n")
91 }
92
93 Declaration::Enum(e) => {
94 let mut lines = vec![format!("enum {} {{", e.name.value)];
95 for (idx, variant) in e.variants.iter().enumerate() {
96 if idx > 0 {
97 let prev = &e.variants[idx - 1];
98 push_gap_content(
99 source,
100 prev.span.end,
101 variant.span.start,
102 &mut lines,
103 " ",
104 );
105 }
106 let mut line = format!(" {}", variant.name.value);
107 if let Some(c) = trailing_inline_comment(source, variant.span.end) {
108 line.push_str(" ");
109 line.push_str(&c);
110 }
111 lines.push(line);
112 }
113 lines.push("}".to_string());
114 lines.join("\n")
115 }
116
117 Declaration::Model(model) => {
118 let max_name = model
119 .fields
120 .iter()
121 .map(|f| f.name.value.len())
122 .max()
123 .unwrap_or(0);
124 let max_type = model
125 .fields
126 .iter()
127 .map(|f| format_field_type_with_modifier(&f.field_type, f.modifier).len())
128 .max()
129 .unwrap_or(0);
130
131 let mut lines = vec![format!("model {} {{", model.name.value)];
132
133 for (idx, field) in model.fields.iter().enumerate() {
134 if idx > 0 {
136 let prev = &model.fields[idx - 1];
137 push_gap_content(source, prev.span.end, field.span.start, &mut lines, " ");
138 }
139
140 let type_str =
141 format_field_type_with_modifier(&field.field_type, field.modifier);
142 let attrs: Vec<String> =
143 field.attributes.iter().map(format_field_attr).collect();
144
145 let name_padding = max_name - field.name.value.len() + 1;
146
147 let mut line = if attrs.is_empty() {
148 format!(
149 " {}{}{}",
150 field.name.value,
151 " ".repeat(name_padding),
152 type_str,
153 )
154 } else {
155 let type_padding = max_type - type_str.len() + 1;
156 format!(
157 " {}{}{}{}{}",
158 field.name.value,
159 " ".repeat(name_padding),
160 type_str,
161 " ".repeat(type_padding),
162 attrs.join(" "),
163 )
164 };
165
166 line = line.trim_end().to_string();
167
168 if let Some(c) = trailing_inline_comment(source, field.span.end) {
169 line.push_str(" ");
170 line.push_str(&c);
171 }
172
173 lines.push(line);
174 }
175
176 if !model.attributes.is_empty() && !model.fields.is_empty() {
177 lines.push(String::new());
178 }
179 for attr in &model.attributes {
180 lines.push(format!(" {}", format_model_attr(attr)));
181 }
182
183 lines.push("}".to_string());
184 lines.join("\n")
185 }
186
187 Declaration::Type(type_decl) => format_type_decl(type_decl, source),
188 };
189
190 parts.push((block, decl.span().end));
191 }
192
193 let mut out = String::new();
194
195 if let Some((_, _)) = parts.first() {
196 if let Some(first_decl) = schema.declarations.first() {
197 let leading = top_level_comments(source, 0, first_decl.span().start);
198 for line in &leading {
199 out.push_str(line);
200 out.push('\n');
201 }
202 if !leading.is_empty() {
203 out.push('\n');
204 }
205 }
206 }
207
208 for (i, (block, span_end)) in parts.iter().enumerate() {
209 if i > 0 {
210 let prev_end = parts[i - 1].1;
211 let curr_start = schema.declarations[i].span().start;
212 let gap_comments = top_level_comments(source, prev_end, curr_start);
213 if gap_comments.is_empty() {
214 out.push_str("\n\n");
215 } else {
216 out.push_str("\n\n");
217 for comment in &gap_comments {
218 out.push_str(comment);
219 out.push('\n');
220 }
221 out.push('\n');
222 }
223 }
224 out.push_str(block);
225 let _ = span_end;
226 }
227
228 if let Some(last_decl) = schema.declarations.last() {
229 let trailing = top_level_comments(source, last_decl.span().end, source.len());
230 for comment in &trailing {
231 out.push('\n');
232 out.push_str(comment);
233 }
234 }
235
236 if !out.ends_with('\n') {
237 out.push('\n');
238 }
239 out
240}
241
242fn trailing_inline_comment(source: &str, pos: usize) -> Option<String> {
246 let rest = source.get(pos..)?;
247 let line_end = rest.find('\n').unwrap_or(rest.len());
248 let rest_of_line = rest[..line_end].trim();
249 if rest_of_line.starts_with("//") {
250 Some(rest_of_line.to_string())
251 } else {
252 None
253 }
254}
255
256fn push_gap_content(
264 source: &str,
265 prev_end: usize,
266 curr_start: usize,
267 lines: &mut Vec<String>,
268 indent: &str,
269) {
270 if prev_end >= curr_start {
271 return;
272 }
273 let gap = match source.get(prev_end..curr_start) {
274 Some(s) => s,
275 None => return,
276 };
277
278 let after_first_nl = match gap.find('\n') {
279 Some(pos) => &gap[pos + 1..],
280 None => return,
281 };
282
283 let mut comment_lines: Vec<String> = Vec::new();
284 let mut blank_before_first = false;
285
286 for raw_line in after_first_nl.lines() {
287 let trimmed = raw_line.trim();
288 if trimmed.starts_with("//") {
289 comment_lines.push(trimmed.to_string());
290 } else if trimmed.is_empty() && comment_lines.is_empty() {
291 blank_before_first = true;
292 }
293 }
294
295 if !comment_lines.is_empty() {
296 if blank_before_first {
297 lines.push(String::new());
298 }
299 for c in &comment_lines {
300 lines.push(format!("{}{}", indent, c));
301 }
302 } else {
303 let nl_count = after_first_nl.chars().filter(|&c| c == '\n').count();
304 if nl_count >= 1 {
305 lines.push(String::new());
306 }
307 }
308}
309
310fn top_level_comments(source: &str, from: usize, to: usize) -> Vec<String> {
317 let slice = match source.get(from..to) {
318 Some(s) => s,
319 None => return Vec::new(),
320 };
321
322 let mut result: Vec<String> = Vec::new();
323 for raw_line in slice.lines() {
324 let trimmed = raw_line.trim();
325 if trimmed.starts_with("//") {
326 result.push(trimmed.to_string());
327 } else if trimmed.is_empty() && !result.is_empty() {
328 result.push(String::new());
329 }
330 }
331
332 while result
333 .last()
334 .map(|s: &String| s.is_empty())
335 .unwrap_or(false)
336 {
337 result.pop();
338 }
339
340 result
341}
342
343fn format_type_decl(type_decl: &TypeDecl, source: &str) -> String {
345 let max_name = type_decl
346 .fields
347 .iter()
348 .map(|f| f.name.value.len())
349 .max()
350 .unwrap_or(0);
351 let max_type = type_decl
352 .fields
353 .iter()
354 .map(|f| format_field_type_with_modifier(&f.field_type, f.modifier).len())
355 .max()
356 .unwrap_or(0);
357
358 let mut lines = vec![format!("type {} {{", type_decl.name.value)];
359
360 for (idx, field) in type_decl.fields.iter().enumerate() {
361 if idx > 0 {
362 let prev = &type_decl.fields[idx - 1];
363 push_gap_content(source, prev.span.end, field.span.start, &mut lines, " ");
364 }
365
366 let type_str = format_field_type_with_modifier(&field.field_type, field.modifier);
367 let attrs: Vec<String> = field.attributes.iter().map(format_field_attr).collect();
368
369 let name_padding = max_name - field.name.value.len() + 1;
370
371 let mut line = if attrs.is_empty() {
372 format!(
373 " {}{}{}",
374 field.name.value,
375 " ".repeat(name_padding),
376 type_str,
377 )
378 } else {
379 let type_padding = max_type - type_str.len() + 1;
380 format!(
381 " {}{}{}{}{}",
382 field.name.value,
383 " ".repeat(name_padding),
384 type_str,
385 " ".repeat(type_padding),
386 attrs.join(" "),
387 )
388 };
389
390 line = line.trim_end().to_string();
391
392 if let Some(c) = trailing_inline_comment(source, field.span.end) {
393 line.push_str(" ");
394 line.push_str(&c);
395 }
396
397 lines.push(line);
398 }
399
400 lines.push("}".to_string());
401 lines.join("\n")
402}
403
404fn format_field_type_with_modifier(ft: &FieldType, modifier: FieldModifier) -> String {
405 let base = ft.to_string();
406 match modifier {
407 FieldModifier::None => base,
408 FieldModifier::Optional => format!("{}?", base),
409 FieldModifier::NotNull => format!("{}!", base),
410 FieldModifier::Array => format!("{}[]", base),
411 }
412}
413
414fn format_field_attr(attr: &FieldAttribute) -> String {
416 match attr {
417 FieldAttribute::Id => "@id".to_string(),
418 FieldAttribute::Unique => "@unique".to_string(),
419
420 FieldAttribute::Default(expr, _) => format!("@default({})", format_expr(expr)),
421
422 FieldAttribute::Map(name) => format!("@map(\"{}\")", name),
423
424 FieldAttribute::Store { strategy, .. } => match strategy {
425 StorageStrategy::Json => "@store(json)".to_string(),
426 StorageStrategy::Native => "@store(native)".to_string(),
427 },
428
429 FieldAttribute::Relation {
430 name,
431 fields,
432 references,
433 on_delete,
434 on_update,
435 ..
436 } => {
437 let mut args: Vec<String> = Vec::new();
438
439 if let Some(n) = name {
440 args.push(format!("name: \"{}\"", n));
441 }
442 if let Some(flds) = fields {
443 let names: Vec<_> = flds.iter().map(|i| i.value.clone()).collect();
444 args.push(format!("fields: [{}]", names.join(", ")));
445 }
446 if let Some(refs) = references {
447 let names: Vec<_> = refs.iter().map(|i| i.value.clone()).collect();
448 args.push(format!("references: [{}]", names.join(", ")));
449 }
450 if let Some(action) = on_delete {
451 args.push(format!("onDelete: {}", format_referential_action(action)));
452 }
453 if let Some(action) = on_update {
454 args.push(format!("onUpdate: {}", format_referential_action(action)));
455 }
456
457 format!("@relation({})", args.join(", "))
458 }
459
460 FieldAttribute::UpdatedAt { .. } => "@updatedAt".to_string(),
461
462 FieldAttribute::Computed { expr, kind, .. } => {
463 let kind_str = match kind {
464 ComputedKind::Stored => "Stored",
465 ComputedKind::Virtual => "Virtual",
466 };
467 format!("@computed({}, {})", expr, kind_str)
468 }
469
470 FieldAttribute::Check { expr, .. } => format!("@check({})", expr),
471 }
472}
473
474fn format_model_attr(attr: &ModelAttribute) -> String {
476 match attr {
477 ModelAttribute::Map(name) => format!("@@map(\"{}\")", name),
478 ModelAttribute::Id(fields) => {
479 let names: Vec<_> = fields.iter().map(|i| i.value.clone()).collect();
480 format!("@@id([{}])", names.join(", "))
481 }
482 ModelAttribute::Unique(fields) => {
483 let names: Vec<_> = fields.iter().map(|i| i.value.clone()).collect();
484 format!("@@unique([{}])", names.join(", "))
485 }
486 ModelAttribute::Index {
487 fields,
488 index_type,
489 name,
490 map,
491 } => {
492 let names: Vec<_> = fields.iter().map(|i| i.value.clone()).collect();
493 let mut s = format!("@@index([{}])", names.join(", "));
494 if index_type.is_some() || name.is_some() || map.is_some() {
495 s.pop();
496 if let Some(t) = index_type {
497 s.push_str(&format!(", type: {}", t.value));
498 }
499 if let Some(n) = name {
500 s.push_str(&format!(", name: \"{}\"", n));
501 }
502 if let Some(m) = map {
503 s.push_str(&format!(", map: \"{}\"", m));
504 }
505 s.push(')');
506 }
507 s
508 }
509
510 ModelAttribute::Check { expr, .. } => format!("@@check({})", expr),
511 }
512}
513
514fn format_referential_action(action: &ReferentialAction) -> &'static str {
516 match action {
517 ReferentialAction::Cascade => "Cascade",
518 ReferentialAction::Restrict => "Restrict",
519 ReferentialAction::NoAction => "NoAction",
520 ReferentialAction::SetNull => "SetNull",
521 ReferentialAction::SetDefault => "SetDefault",
522 }
523}
524
525pub(crate) fn format_expr(expr: &Expr) -> String {
527 match expr {
528 Expr::Literal(lit) => format_literal(lit),
529
530 Expr::FunctionCall { name, args, .. } => {
531 if args.is_empty() {
532 format!("{}()", name.value)
533 } else {
534 let formatted: Vec<_> = args.iter().map(format_expr).collect();
535 format!("{}({})", name.value, formatted.join(", "))
536 }
537 }
538
539 Expr::Array { elements, .. } => {
540 let formatted: Vec<_> = elements.iter().map(format_expr).collect();
541 format!("[{}]", formatted.join(", "))
542 }
543
544 Expr::NamedArg { name, value, .. } => {
545 format!("{}: {}", name.value, format_expr(value))
546 }
547
548 Expr::Ident(ident) => ident.value.clone(),
549 }
550}
551
552fn format_literal(lit: &Literal) -> String {
554 match lit {
555 Literal::String(s, _) => format!("\"{}\"", s),
556 Literal::Number(n, _) => n.clone(),
557 Literal::Boolean(b, _) => b.to_string(),
558 }
559}