1use crate::writer::{Writer, comment_lines, escape_string, verbatim_lines};
22use alloc::format;
23use alloc::string::{String, ToString};
24use alloc::vec::Vec;
25use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
26use zerodds_lang_cst::typescript::{
27 Block, Class, ClassMember, Constructor, Decl, Enum, EnumMember, Expr, Field, File, Function,
28 Import, IndexSignature, Interface, Method, MethodSignature, Namespace, Param, Property,
29 PropertyName, Signature, Statement, SwitchCase, Type, TypeAlias, TypeParam, Variable,
30};
31
32#[must_use]
34pub fn file(node: &File) -> String {
35 let mut w = writer();
36 doc(&mut w, &node.doc);
37 for import in &node.imports {
38 w.line(&import_text(import));
39 }
40 if !node.imports.is_empty() {
41 w.blank_line();
42 }
43 render_decls(&mut w, &node.decls);
44 w.finish()
45}
46
47#[must_use]
49pub fn decl(node: &Decl) -> String {
50 let mut w = writer();
51 render_decl(&mut w, node);
52 w.finish()
53}
54
55#[must_use]
57pub fn type_(node: &Type) -> String {
58 type_text(node)
59}
60
61#[must_use]
63pub fn expr(node: &Expr) -> String {
64 expr_text(node)
65}
66
67fn writer() -> Writer {
69 Writer::new(" ")
70}
71
72fn import_text(node: &Import) -> String {
73 let mut out = String::from("import ");
74 if node.type_only {
75 out.push_str("type ");
76 }
77 let mut clauses: Vec<String> = Vec::new();
78 if let Some(default) = &node.default {
79 clauses.push(default.as_str().to_string());
80 }
81 if let Some(namespace) = &node.namespace {
82 let mut text = String::from("* as ");
83 text.push_str(namespace.as_str());
84 clauses.push(text);
85 }
86 if !node.names.is_empty() {
87 let parts: Vec<String> = node
88 .names
89 .iter()
90 .map(|name| {
91 let mut text = String::new();
92 if name.type_only {
93 text.push_str("type ");
94 }
95 text.push_str(name.name.as_str());
96 if let Some(alias) = &name.alias {
97 text.push_str(" as ");
98 text.push_str(alias.as_str());
99 }
100 text
101 })
102 .collect();
103 let mut text = String::from("{ ");
104 text.push_str(&parts.join(", "));
105 text.push_str(" }");
106 clauses.push(text);
107 }
108 out.push_str(&clauses.join(", "));
109 out.push_str(" from \"");
110 out.push_str(&escape_string(&node.module));
111 out.push_str("\";");
112 out
113}
114
115fn render_decls(w: &mut Writer, decls: &[Decl]) {
124 let mut previous_had_body = false;
125 for (index, node) in decls.iter().enumerate() {
126 let has_body = matches!(
127 node,
128 Decl::Interface(_)
129 | Decl::Class(_)
130 | Decl::Enum(_)
131 | Decl::Namespace(_)
132 | Decl::Function(_)
133 );
134 if index > 0 && (has_body || previous_had_body) {
135 w.blank_line();
136 }
137 render_decl(w, node);
138 previous_had_body = has_body;
139 }
140}
141
142fn render_decl(w: &mut Writer, node: &Decl) {
143 match node {
144 Decl::Interface(inner) => render_interface(w, inner),
145 Decl::Class(inner) => render_class(w, inner),
146 Decl::TypeAlias(inner) => render_type_alias(w, inner),
147 Decl::Enum(inner) => render_enum(w, inner),
148 Decl::Variable(inner) => render_variable(w, inner),
149 Decl::Function(inner) => render_function(w, inner),
150 Decl::Namespace(inner) => render_namespace(w, inner),
151 Decl::Statement(inner) => render_statement(w, inner),
152 Decl::Comment(inner) => doc(w, inner),
153 Decl::Verbatim(text) => verbatim_lines(w, text),
154 }
155}
156
157fn render_interface(w: &mut Writer, node: &Interface) {
158 doc(w, &node.doc);
159 let mut head = String::new();
160 if node.exported {
161 head.push_str("export ");
162 }
163 head.push_str("interface ");
164 head.push_str(node.name.as_str());
165 head.push_str(&generics_text(&node.generics));
166 if !node.extends.is_empty() {
167 head.push_str(" extends ");
168 let parts: Vec<String> = node.extends.iter().map(type_text).collect();
169 head.push_str(&parts.join(", "));
170 }
171 if node.members.is_empty() {
174 head.push_str(" {}");
175 w.line(&head);
176 return;
177 }
178 head.push_str(" {");
179 w.line(&head);
180 w.indent();
181 for member in &node.members {
182 render_signature(w, member);
183 }
184 w.dedent();
185 w.line("}");
186}
187
188fn render_class(w: &mut Writer, node: &Class) {
189 doc(w, &node.doc);
190 let mut head = String::new();
191 if node.exported {
192 head.push_str("export ");
193 }
194 if node.abstract_ {
195 head.push_str("abstract ");
196 }
197 head.push_str("class ");
198 head.push_str(node.name.as_str());
199 head.push_str(&generics_text(&node.generics));
200 if let Some(extends) = &node.extends {
201 head.push_str(" extends ");
202 head.push_str(&type_text(extends));
203 }
204 if !node.implements.is_empty() {
205 head.push_str(" implements ");
206 let parts: Vec<String> = node.implements.iter().map(type_text).collect();
207 head.push_str(&parts.join(", "));
208 }
209 if node.members.is_empty() {
210 head.push_str(" {}");
211 w.line(&head);
212 return;
213 }
214 head.push_str(" {");
215 w.line(&head);
216 w.indent();
217 render_class_members(w, &node.members);
218 w.dedent();
219 w.line("}");
220}
221
222fn render_class_members(w: &mut Writer, members: &[ClassMember]) {
223 let mut previous_had_body = false;
224 for (index, member) in members.iter().enumerate() {
225 let has_body = matches!(member, ClassMember::Method(_) | ClassMember::Constructor(_));
226 if index > 0 && (has_body || previous_had_body) {
227 w.blank_line();
228 }
229 match member {
230 ClassMember::Field(field) => render_field(w, field),
231 ClassMember::Method(method) => render_method(w, method),
232 ClassMember::Constructor(constructor) => render_constructor(w, constructor),
233 ClassMember::Comment(text) => doc(w, text),
234 ClassMember::Verbatim(text) => verbatim_lines(w, text),
235 }
236 previous_had_body = has_body;
237 }
238}
239
240fn render_field(w: &mut Writer, node: &Field) {
241 doc(w, &node.doc);
242 let mut line = String::new();
243 if let Some(accessibility) = node.accessibility {
244 line.push_str(accessibility.as_str());
245 line.push(' ');
246 }
247 if node.static_ {
248 line.push_str("static ");
249 }
250 if node.readonly {
251 line.push_str("readonly ");
252 }
253 line.push_str(&property_name_text(&node.name));
254 if node.optional {
255 line.push('?');
256 }
257 if node.definite {
258 line.push('!');
259 }
260 if let Some(type_) = &node.type_ {
261 line.push_str(": ");
262 line.push_str(&type_text(type_));
263 }
264 if let Some(value) = &node.value {
265 line.push_str(" = ");
266 line.push_str(&expr_text(value));
267 }
268 line.push(';');
269 w.line(&line);
270}
271
272fn render_method(w: &mut Writer, node: &Method) {
273 doc(w, &node.doc);
274 let mut head = String::new();
275 if let Some(accessibility) = node.accessibility {
276 head.push_str(accessibility.as_str());
277 head.push(' ');
278 }
279 if node.static_ {
280 head.push_str("static ");
281 }
282 if node.abstract_ {
283 head.push_str("abstract ");
284 }
285 head.push_str(&property_name_text(&node.name));
286 head.push_str(&generics_text(&node.generics));
287 head.push('(');
288 head.push_str(¶ms_text(&node.params));
289 head.push(')');
290 if let Some(returns) = &node.returns {
291 head.push_str(": ");
292 head.push_str(&type_text(returns));
293 }
294 match &node.body {
295 None => {
296 head.push(';');
297 w.line(&head);
298 }
299 Some(block) => {
300 head.push_str(" {");
301 w.line(&head);
302 w.indent();
303 render_block(w, block);
304 w.dedent();
305 w.line("}");
306 }
307 }
308}
309
310fn render_constructor(w: &mut Writer, node: &Constructor) {
311 doc(w, &node.doc);
312 let mut head = String::new();
313 if let Some(accessibility) = node.accessibility {
314 head.push_str(accessibility.as_str());
315 head.push(' ');
316 }
317 head.push_str("constructor(");
318 head.push_str(¶ms_text(&node.params));
319 head.push_str(") {");
320 w.line(&head);
321 w.indent();
322 render_block(w, &node.body);
323 w.dedent();
324 w.line("}");
325}
326
327fn render_type_alias(w: &mut Writer, node: &TypeAlias) {
328 doc(w, &node.doc);
329 let mut line = String::new();
330 if node.exported {
331 line.push_str("export ");
332 }
333 line.push_str("type ");
334 line.push_str(node.name.as_str());
335 line.push_str(&generics_text(&node.generics));
336 line.push_str(" = ");
337 line.push_str(&type_text(&node.type_));
338 line.push(';');
339 w.line(&line);
340}
341
342fn render_enum(w: &mut Writer, node: &Enum) {
343 doc(w, &node.doc);
344 let mut head = String::new();
345 if node.exported {
346 head.push_str("export ");
347 }
348 if node.const_ {
349 head.push_str("const ");
350 }
351 head.push_str("enum ");
352 head.push_str(node.name.as_str());
353 if node.members.is_empty() {
354 head.push_str(" {}");
355 w.line(&head);
356 return;
357 }
358 head.push_str(" {");
359 w.line(&head);
360 w.indent();
361 for member in &node.members {
362 render_enum_member(w, member);
363 }
364 w.dedent();
365 w.line("}");
366}
367
368fn render_enum_member(w: &mut Writer, node: &EnumMember) {
372 doc(w, &node.doc);
373 let mut line = property_name_text(&node.name);
374 if let Some(value) = &node.value {
375 line.push_str(" = ");
376 line.push_str(&expr_text(value));
377 }
378 line.push(',');
379 w.line(&line);
380}
381
382fn render_variable(w: &mut Writer, node: &Variable) {
383 doc(w, &node.doc);
384 let mut line = String::new();
385 if node.exported {
386 line.push_str("export ");
387 }
388 if node.declare {
389 line.push_str("declare ");
390 }
391 line.push_str(node.kind.as_str());
392 line.push(' ');
393 line.push_str(node.name.as_str());
394 if let Some(type_) = &node.type_ {
395 line.push_str(": ");
396 line.push_str(&type_text(type_));
397 }
398 if let Some(value) = &node.value {
399 line.push_str(" = ");
400 line.push_str(&expr_text(value));
401 }
402 line.push(';');
403 w.line(&line);
404}
405
406fn render_function(w: &mut Writer, node: &Function) {
407 doc(w, &node.doc);
408 let mut head = String::new();
409 if node.exported {
410 head.push_str("export ");
411 }
412 head.push_str("function ");
413 head.push_str(node.name.as_str());
414 head.push_str(&generics_text(&node.generics));
415 head.push('(');
416 head.push_str(¶ms_text(&node.params));
417 head.push(')');
418 if let Some(returns) = &node.returns {
419 head.push_str(": ");
420 head.push_str(&type_text(returns));
421 }
422 match &node.body {
423 None => {
424 head.push(';');
425 w.line(&head);
426 }
427 Some(block) => {
428 head.push_str(" {");
429 w.line(&head);
430 w.indent();
431 render_block(w, block);
432 w.dedent();
433 w.line("}");
434 }
435 }
436}
437
438fn render_namespace(w: &mut Writer, node: &Namespace) {
441 doc(w, &node.doc);
442 let mut head = String::new();
443 if node.exported {
444 head.push_str("export ");
445 }
446 if node.declare {
447 head.push_str("declare ");
448 }
449 head.push_str("namespace ");
450 head.push_str(node.name.as_str());
451 if node.decls.is_empty() {
452 head.push_str(" {}");
453 w.line(&head);
454 return;
455 }
456 head.push_str(" {");
457 w.line(&head);
458 w.indent();
459 render_decls(w, &node.decls);
460 w.dedent();
461 w.line("}");
462}
463
464fn render_signature(w: &mut Writer, node: &Signature) {
465 match node {
466 Signature::Comment(text) => doc(w, text),
467 Signature::Verbatim(text) => verbatim_lines(w, text),
468 Signature::Property(property) => {
469 doc(w, &property.doc);
470 let mut line = property_head(property);
471 line.push(';');
472 w.line(&line);
473 }
474 Signature::Method(method) => {
475 doc(w, &method.doc);
476 let mut line = method_signature_head(method);
477 line.push(';');
478 w.line(&line);
479 }
480 Signature::Index(index) => {
481 doc(w, &index.doc);
482 let mut line = index_signature_head(index);
483 line.push(';');
484 w.line(&line);
485 }
486 }
487}
488
489fn property_head(node: &Property) -> String {
490 let mut out = String::new();
491 if node.readonly {
492 out.push_str("readonly ");
493 }
494 out.push_str(&property_name_text(&node.name));
495 if node.optional {
496 out.push('?');
497 }
498 out.push_str(": ");
499 out.push_str(&type_text(&node.type_));
500 out
501}
502
503fn method_signature_head(node: &MethodSignature) -> String {
504 let mut out = property_name_text(&node.name);
505 if node.optional {
506 out.push('?');
507 }
508 out.push_str(&generics_text(&node.generics));
509 out.push('(');
510 out.push_str(¶ms_text(&node.params));
511 out.push_str("): ");
512 out.push_str(&type_text(&node.returns));
513 out
514}
515
516fn index_signature_head(node: &IndexSignature) -> String {
517 let mut out = String::new();
518 if node.readonly {
519 out.push_str("readonly ");
520 }
521 out.push('[');
522 out.push_str(node.key.as_str());
523 out.push_str(": ");
524 out.push_str(&type_text(&node.key_type));
525 out.push_str("]: ");
526 out.push_str(&type_text(&node.value_type));
527 out
528}
529
530fn inline_signature_text(node: &Signature) -> String {
537 let (doc, body) = match node {
538 Signature::Property(property) => (&property.doc, property_head(property)),
539 Signature::Method(method) => (&method.doc, method_signature_head(method)),
540 Signature::Index(index) => (&index.doc, index_signature_head(index)),
541 Signature::Comment(text) => (text, String::new()),
542 Signature::Verbatim(text) => return text.lines().collect::<Vec<_>>().join(" "),
545 };
546 if doc.is_empty() {
547 return body;
548 }
549 let mut out = String::from("/* ");
550 out.push_str(&doc.as_lines().join(" "));
551 out.push_str(" */");
552 if !body.is_empty() {
553 out.push(' ');
554 out.push_str(&body);
555 }
556 out
557}
558
559fn render_block(w: &mut Writer, block: &Block) {
570 for statement in &block.statements {
571 render_statement(w, statement);
572 }
573}
574
575fn render_statement(w: &mut Writer, node: &Statement) {
586 match node {
587 Statement::Declare {
588 kind,
589 name,
590 type_,
591 value,
592 } => {
593 let mut line = kind.as_str().to_string();
594 line.push(' ');
595 line.push_str(name.as_str());
596 if let Some(type_) = type_ {
597 line.push_str(": ");
598 line.push_str(&type_text(type_));
599 }
600 if let Some(value) = value {
601 line.push_str(" = ");
602 line.push_str(&expr_text(value));
603 }
604 line.push(';');
605 w.line(&line);
606 }
607 Statement::Expr(value) => {
608 let mut line = expr_text(value);
609 line.push(';');
610 w.line(&line);
611 }
612 Statement::Return(value) => {
613 let mut line = String::from("return");
614 if let Some(value) = value {
615 line.push(' ');
616 line.push_str(&expr_text(value));
617 }
618 line.push(';');
619 w.line(&line);
620 }
621 Statement::If {
622 condition,
623 then,
624 otherwise,
625 } => {
626 let mut head = String::from("if (");
627 head.push_str(&expr_text(condition));
628 head.push_str(") {");
629 w.line(&head);
630 w.indent();
631 render_block(w, then);
632 w.dedent();
633 match otherwise {
634 Some(block) => {
635 w.line("} else {");
636 w.indent();
637 render_block(w, block);
638 w.dedent();
639 w.line("}");
640 }
641 None => w.line("}"),
642 }
643 }
644 Statement::Break => w.line("break;"),
645 Statement::For {
646 counter,
647 bound,
648 body,
649 } => {
650 let name = counter.as_str();
651 w.line(&format!(
652 "for (let {name} = 0; {name} < {}; {name} += 1) {{",
653 expr_text(bound)
654 ));
655 w.indent();
656 render_block(w, body);
657 w.dedent();
658 w.line("}");
659 }
660 Statement::While { condition, body } => {
661 w.line(&format!("while ({}) {{", expr_text(condition)));
662 w.indent();
663 render_block(w, body);
664 w.dedent();
665 w.line("}");
666 }
667 Statement::ForOf {
668 kind,
669 binding,
670 range,
671 body,
672 } => {
673 let mut head = String::from("for (");
674 head.push_str(kind.as_str());
675 head.push(' ');
676 head.push_str(binding.as_str());
677 head.push_str(" of ");
678 head.push_str(&expr_text(range));
679 head.push_str(") {");
680 w.line(&head);
681 w.indent();
682 render_block(w, body);
683 w.dedent();
684 w.line("}");
685 }
686 Statement::Switch { value, cases } => {
687 let mut head = String::from("switch (");
688 head.push_str(&expr_text(value));
689 head.push_str(") {");
690 w.line(&head);
691 for case in cases {
692 render_case(w, case);
693 }
694 w.line("}");
695 }
696 Statement::Throw(value) => {
697 let mut line = String::from("throw ");
698 line.push_str(&expr_text(value));
699 line.push(';');
700 w.line(&line);
701 }
702 Statement::Comment(text) => doc(w, text),
703 }
704}
705
706fn render_case(w: &mut Writer, case: &SwitchCase) {
707 if case.labels.is_empty() {
708 w.line("default:");
709 } else {
710 for label in &case.labels {
711 let mut line = String::from("case ");
712 line.push_str(&expr_text(label));
713 line.push(':');
714 w.line(&line);
715 }
716 }
717 w.indent();
718 render_block(w, &case.body);
719 if case.breaks {
720 w.line("break;");
721 }
722 w.dedent();
723}
724
725fn generics_text(params: &[TypeParam]) -> String {
726 if params.is_empty() {
727 return String::new();
728 }
729 let parts: Vec<String> = params
730 .iter()
731 .map(|param| {
732 let mut text = param.name.as_str().to_string();
733 if let Some(extends) = ¶m.extends {
734 text.push_str(" extends ");
735 text.push_str(&type_text(extends));
736 }
737 if let Some(default) = ¶m.default {
738 text.push_str(" = ");
739 text.push_str(&type_text(default));
740 }
741 text
742 })
743 .collect();
744 let mut out = String::from("<");
745 out.push_str(&parts.join(", "));
746 out.push('>');
747 out
748}
749
750fn params_text(params: &[Param]) -> String {
754 let parts: Vec<String> = params
755 .iter()
756 .map(|param| {
757 let mut text = String::new();
758 if param.rest {
759 text.push_str("...");
760 }
761 text.push_str(param.name.as_str());
762 if param.optional {
763 text.push('?');
764 }
765 if let Some(type_) = ¶m.type_ {
766 text.push_str(": ");
767 text.push_str(&type_text(type_));
768 }
769 if let Some(default) = ¶m.default {
770 text.push_str(" = ");
771 text.push_str(&expr_text(default));
772 }
773 text
774 })
775 .collect();
776 parts.join(", ")
777}
778
779fn property_name_text(name: &PropertyName) -> String {
781 match name {
782 PropertyName::Ident(ident) => ident.as_str().to_string(),
783 PropertyName::Quoted(text) => {
784 let mut out = String::from("\"");
785 out.push_str(&escape_string(text));
786 out.push('"');
787 out
788 }
789 }
790}
791
792fn path_text(path: &Path) -> String {
798 let parts: Vec<&str> = path.segments().iter().map(Ident::as_str).collect();
799 parts.join(".")
800}
801
802fn type_text(node: &Type) -> String {
806 match node {
807 Type::Keyword(keyword) => keyword.as_str().to_string(),
808 Type::Named { path, args } => {
809 let mut out = path_text(path);
810 if !args.is_empty() {
811 out.push('<');
812 let parts: Vec<String> = args.iter().map(type_text).collect();
813 out.push_str(&parts.join(", "));
814 out.push('>');
815 }
816 out
817 }
818 Type::Array(element) => {
819 let mut out = type_text(element);
820 out.push_str("[]");
821 out
822 }
823 Type::ReadonlyArray(element) => {
824 let mut out = String::from("readonly ");
825 out.push_str(&type_text(element));
826 out.push_str("[]");
827 out
828 }
829 Type::Tuple(elements) => {
830 let parts: Vec<String> = elements.iter().map(type_text).collect();
831 let mut out = String::from("[");
832 out.push_str(&parts.join(", "));
833 out.push(']');
834 out
835 }
836 Type::Union(arms) => {
837 let parts: Vec<String> = arms.iter().map(type_text).collect();
838 parts.join(" | ")
839 }
840 Type::Intersection(arms) => {
841 let parts: Vec<String> = arms.iter().map(type_text).collect();
842 parts.join(" & ")
843 }
844 Type::Object(members) => {
845 if members.is_empty() {
846 return "{}".to_string();
847 }
848 let parts: Vec<String> = members.iter().map(inline_signature_text).collect();
849 let mut out = String::from("{ ");
850 out.push_str(&parts.join("; "));
851 out.push_str(" }");
852 out
853 }
854 Type::Literal(literal) => literal_text(literal),
855 Type::Function { params, returns } => {
856 let mut out = String::from("(");
857 out.push_str(¶ms_text(params));
858 out.push_str(") => ");
859 out.push_str(&type_text(returns));
860 out
861 }
862 Type::Parenthesised(inner) => {
863 let mut out = String::from("(");
864 out.push_str(&type_text(inner));
865 out.push(')');
866 out
867 }
868 Type::TypeOf(path) => {
869 let mut out = String::from("typeof ");
870 out.push_str(&path_text(path));
871 out
872 }
873 Type::KeyOf(inner) => {
874 let mut out = String::from("keyof ");
875 out.push_str(&type_text(inner));
876 out
877 }
878 Type::Predicate { param, type_ } => {
879 let mut out = param.as_str().to_string();
880 out.push_str(" is ");
881 out.push_str(&type_text(type_));
882 out
883 }
884 Type::Indexed { object, index } => {
885 let mut out = type_text(object);
886 out.push('[');
887 out.push_str(&type_text(index));
888 out.push(']');
889 out
890 }
891 }
892}
893
894#[allow(clippy::too_many_lines)]
901fn expr_text(node: &Expr) -> String {
902 match node {
903 Expr::Literal(literal) => literal_text(literal),
904 Expr::Path(path) => path_text(path),
905 Expr::Array(elements) => {
906 let parts: Vec<String> = elements.iter().map(expr_text).collect();
907 let mut out = String::from("[");
908 out.push_str(&parts.join(", "));
909 out.push(']');
910 out
911 }
912 Expr::Object(entries) => {
913 if entries.is_empty() {
914 return "{}".to_string();
915 }
916 let parts: Vec<String> = entries
917 .iter()
918 .map(|(name, value)| {
919 let mut text = property_name_text(name);
920 text.push_str(": ");
921 text.push_str(&expr_text(value));
922 text
923 })
924 .collect();
925 let mut out = String::from("{ ");
926 out.push_str(&parts.join(", "));
927 out.push_str(" }");
928 out
929 }
930 Expr::New { callee, args } => {
931 let mut out = String::from("new ");
932 out.push_str(&path_text(callee));
933 out.push('(');
934 let parts: Vec<String> = args.iter().map(expr_text).collect();
935 out.push_str(&parts.join(", "));
936 out.push(')');
937 out
938 }
939 Expr::Call { callee, args } => {
940 let mut out = expr_text(callee);
941 out.push('(');
942 let parts: Vec<String> = args.iter().map(expr_text).collect();
943 out.push_str(&parts.join(", "));
944 out.push(')');
945 out
946 }
947 Expr::Member { object, name } => {
948 let mut out = expr_text(object);
949 out.push('.');
950 out.push_str(name.as_str());
951 out
952 }
953 Expr::Index { object, index } => {
954 let mut out = expr_text(object);
955 out.push('[');
956 out.push_str(&expr_text(index));
957 out.push(']');
958 out
959 }
960 Expr::Paren(inner) => {
961 let mut out = String::from("(");
962 out.push_str(&expr_text(inner));
963 out.push(')');
964 out
965 }
966 Expr::Binary { op, lhs, rhs } => {
967 let mut out = expr_text(lhs);
968 out.push(' ');
969 out.push_str(op);
970 out.push(' ');
971 out.push_str(&expr_text(rhs));
972 out
973 }
974 Expr::Unary { op, value } => {
975 let mut out = (*op).to_string();
976 out.push_str(&expr_text(value));
977 out
978 }
979 Expr::As { value, type_ } => {
980 let mut out = expr_text(value);
981 out.push_str(" as ");
982 out.push_str(&type_text(type_));
983 out
984 }
985 Expr::AsConst(value) => {
986 let mut out = expr_text(value);
987 out.push_str(" as const");
988 out
989 }
990 Expr::Ternary {
991 condition,
992 then,
993 otherwise,
994 } => {
995 let mut out = expr_text(condition);
996 out.push_str(" ? ");
997 out.push_str(&expr_text(then));
998 out.push_str(" : ");
999 out.push_str(&expr_text(otherwise));
1000 out
1001 }
1002 Expr::Arrow { params, body } => {
1003 let parts: Vec<&str> = params.iter().map(Ident::as_str).collect();
1004 let mut out = String::from("(");
1005 out.push_str(&parts.join(", "));
1006 out.push_str(") => ");
1007 out.push_str(&expr_text(body));
1008 out
1009 }
1010 Expr::Parenthesised(inner) => {
1011 let mut out = String::from("(");
1012 out.push_str(&expr_text(inner));
1013 out.push(')');
1014 out
1015 }
1016 Expr::NonNull(inner) => {
1017 let mut out = expr_text(inner);
1018 out.push('!');
1019 out
1020 }
1021 Expr::This => "this".to_string(),
1022 Expr::Undefined => "undefined".to_string(),
1023 }
1024}
1025
1026fn literal_text(literal: &Literal) -> String {
1038 match literal {
1039 Literal::Int(text) | Literal::Float(text) => text.clone(),
1042 Literal::Bool(value) => {
1043 if *value {
1044 "true".to_string()
1045 } else {
1046 "false".to_string()
1047 }
1048 }
1049 Literal::Char(ch) => {
1050 let mut out = String::from("\"");
1051 out.push_str(&escape_string(&ch.to_string()));
1052 out.push('"');
1053 out
1054 }
1055 Literal::Str(text) => {
1056 let mut out = String::from("\"");
1057 out.push_str(&escape_string(text));
1058 out.push('"');
1059 out
1060 }
1061 Literal::Bytes(bytes) => {
1062 let mut out = String::from("new Uint8Array([");
1063 let parts: Vec<String> = bytes.iter().map(|b| alloc::format!("0x{b:02X}")).collect();
1064 out.push_str(&parts.join(", "));
1065 out.push_str("])");
1066 out
1067 }
1068 Literal::Null => "null".to_string(),
1069 }
1070}
1071
1072fn doc(w: &mut Writer, doc: &Doc) {
1074 if doc.is_empty() {
1075 return;
1076 }
1077 w.line("/**");
1078 comment_lines(w, " * ", doc.as_lines());
1079 w.line(" */");
1080}