wdl_format/
v1.rs

1//! Formatting of WDL v1.x elements.
2
3use std::rc::Rc;
4
5use nonempty::NonEmpty;
6use wdl_ast::SyntaxKind;
7use wdl_ast::SyntaxToken;
8
9pub mod decl;
10pub mod expr;
11pub mod import;
12pub mod meta;
13pub mod sort;
14pub mod r#struct;
15pub mod task;
16pub mod workflow;
17
18use crate::PreToken;
19use crate::TokenStream;
20use crate::Writable as _;
21use crate::element::FormatElement;
22
23/// Formats an [`Ast`](wdl_ast::Ast).
24///
25/// This is the entry point for formatting WDL v1.x files.
26///
27/// # Panics
28///
29/// It will panic if the provided `element` is not a valid WDL v1.x AST.
30pub fn format_ast(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
31    fn last_token_of_element(elem: &FormatElement) -> SyntaxToken {
32        elem.element()
33            .as_node()
34            .expect("all children of an AST should be nodes")
35            .inner()
36            .last_token()
37            .expect("nodes should have tokens")
38    }
39
40    let mut children = element.children().expect("AST children");
41
42    let version_statement = children.next().expect("version statement");
43    assert!(version_statement.element().kind() == SyntaxKind::VersionStatementNode);
44    (&version_statement).write(stream);
45
46    stream.blank_line();
47
48    let mut imports = Vec::new();
49    let mut remainder = Vec::new();
50
51    for child in children {
52        match child.element().kind() {
53            SyntaxKind::ImportStatementNode => imports.push(child),
54            _ => remainder.push(child),
55        }
56    }
57
58    imports.sort_by(|a, b| {
59        let a = a
60            .element()
61            .as_node()
62            .expect("import statement node")
63            .as_import_statement()
64            .expect("import statement");
65        let b = b
66            .element()
67            .as_node()
68            .expect("import statement node")
69            .as_import_statement()
70            .expect("import statement");
71        let a_uri = a
72            .uri()
73            .text()
74            .expect("import uri should not be interpolated");
75        let b_uri = b
76            .uri()
77            .text()
78            .expect("import uri should not be interpolated");
79        a_uri.text().cmp(b_uri.text())
80    });
81
82    stream.ignore_trailing_blank_lines();
83
84    let mut trailing_comments = None;
85
86    for import in imports {
87        (&import).write(stream);
88
89        if trailing_comments.is_none() {
90            trailing_comments = find_trailing_comments(&last_token_of_element(import));
91        }
92    }
93
94    stream.blank_line();
95
96    for child in &remainder {
97        (child).write(stream);
98
99        if trailing_comments.is_none() {
100            trailing_comments = find_trailing_comments(&last_token_of_element(child));
101        }
102
103        stream.blank_line();
104    }
105
106    if let Some(comments) = trailing_comments {
107        stream.trim_end(&PreToken::BlankLine);
108        for comment in comments {
109            stream.push(PreToken::Trivia(crate::Trivia::Comment(
110                crate::Comment::Preceding(Rc::new(comment.text().into())),
111            )));
112            stream.push(PreToken::LineEnd);
113        }
114    }
115}
116
117/// Finds any trailing comments at the end of a WDL document.
118///
119/// Trailing comments are unhandled as they don't fit neatly into the trivia
120/// model used by this crate. [`crate::Comment`]s can only be "preceding" or
121/// "inline", but non-inline comments at the end of a WDL document
122/// have no following element to precede. This will find any such comments and
123/// return them.
124fn find_trailing_comments(token: &SyntaxToken) -> Option<NonEmpty<SyntaxToken>> {
125    let mut next_token = token.next_token();
126    let mut on_next_line = false;
127
128    fn is_comment(token: &SyntaxToken) -> bool {
129        matches!(token.kind(), SyntaxKind::Comment)
130    }
131
132    let mut encountered_comments = Vec::new();
133
134    while let Some(next) = next_token {
135        if !next.kind().is_trivia() {
136            return None;
137        }
138
139        // skip if we are processing an inline comment of the input token
140        let skip = !on_next_line && is_comment(&next);
141        on_next_line = on_next_line || next.text().contains('\n');
142        next_token = next.next_token();
143        if skip {
144            continue;
145        }
146        if is_comment(&next) {
147            encountered_comments.push(next);
148        }
149    }
150
151    NonEmpty::from_vec(encountered_comments)
152}
153
154/// Formats a [`VersionStatement`](wdl_ast::VersionStatement).
155pub fn format_version_statement(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
156    for child in element.children().expect("version statement children") {
157        (&child).write(stream);
158        stream.end_word();
159    }
160    stream.end_line();
161}
162
163/// Formats an [`InputSection`](wdl_ast::v1::InputSection).
164///
165/// # Panics
166///
167/// This will panic if the provided `element` is not a valid WDL v1.x
168/// input section.
169pub fn format_input_section(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
170    let mut children = element.children().expect("input section children");
171
172    let input_keyword = children.next().expect("input section input keyword");
173    assert!(input_keyword.element().kind() == SyntaxKind::InputKeyword);
174    (&input_keyword).write(stream);
175    stream.end_word();
176
177    let open_brace = children.next().expect("input section open brace");
178    assert!(open_brace.element().kind() == SyntaxKind::OpenBrace);
179    (&open_brace).write(stream);
180    stream.increment_indent();
181
182    let mut inputs = Vec::new();
183    let mut close_brace = None;
184
185    for child in children {
186        match child.element().kind() {
187            SyntaxKind::BoundDeclNode | SyntaxKind::UnboundDeclNode => inputs.push(child),
188            SyntaxKind::CloseBrace => close_brace = Some(child),
189            _ => panic!("unexpected input section child"),
190        }
191    }
192
193    inputs.sort_by(|a, b| {
194        let a_decl =
195            wdl_ast::v1::Decl::cast(a.element().as_node().unwrap().inner().clone()).unwrap();
196        let b_decl =
197            wdl_ast::v1::Decl::cast(b.element().as_node().unwrap().inner().clone()).unwrap();
198        sort::compare_decl(&a_decl, &b_decl)
199    });
200    for input in inputs {
201        (&input).write(stream);
202    }
203
204    stream.decrement_indent();
205    (&close_brace.expect("input section close brace")).write(stream);
206    stream.end_line();
207}
208
209/// Formats an [`OutputSection`](wdl_ast::v1::OutputSection).
210///
211/// # Panics
212///
213/// This will panic if the provided `element` is not a valid WDL v1.x
214/// output section.
215pub fn format_output_section(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
216    let mut children = element.children().expect("output section children");
217
218    let output_keyword = children.next().expect("output keyword");
219    assert!(output_keyword.element().kind() == SyntaxKind::OutputKeyword);
220    (&output_keyword).write(stream);
221    stream.end_word();
222
223    let open_brace = children.next().expect("output section open brace");
224    assert!(open_brace.element().kind() == SyntaxKind::OpenBrace);
225    (&open_brace).write(stream);
226    stream.increment_indent();
227
228    for child in children {
229        if child.element().kind() == SyntaxKind::CloseBrace {
230            stream.decrement_indent();
231        } else {
232            assert!(child.element().kind() == SyntaxKind::BoundDeclNode);
233        }
234        (&child).write(stream);
235        stream.end_line();
236    }
237}
238
239/// Formats a [`LiteralInputItem`](wdl_ast::v1::LiteralInputItem).
240///
241/// # Panics
242///
243/// This will panic if the provided `element` is not a valid WDL v1.x
244/// literal input item.
245pub fn format_literal_input_item(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
246    let mut children = element.children().expect("literal input item children");
247
248    let key = children.next().expect("literal input item key");
249    assert!(key.element().kind() == SyntaxKind::Ident);
250    (&key).write(stream);
251
252    let colon = children.next().expect("literal input item colon");
253    assert!(colon.element().kind() == SyntaxKind::Colon);
254    (&colon).write(stream);
255    stream.end_word();
256
257    let hints_node = children.next().expect("literal input item hints node");
258    assert!(hints_node.element().kind() == SyntaxKind::LiteralHintsNode);
259    (&hints_node).write(stream);
260}
261
262/// Formats a [`LiteralInput`](wdl_ast::v1::LiteralInput).
263///
264/// # Panics
265///
266/// This will panic if the provided `element` is not a valid WDL v1.x
267/// literal input.
268pub fn format_literal_input(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
269    let mut children = element.children().expect("literal input children");
270
271    let input_keyword = children.next().expect("literal input keyword");
272    assert!(input_keyword.element().kind() == SyntaxKind::InputKeyword);
273    (&input_keyword).write(stream);
274    stream.end_word();
275
276    let open_brace = children.next().expect("literal input open brace");
277    assert!(open_brace.element().kind() == SyntaxKind::OpenBrace);
278    (&open_brace).write(stream);
279    stream.increment_indent();
280
281    for child in children {
282        if child.element().kind() == SyntaxKind::CloseBrace {
283            stream.decrement_indent();
284        } else {
285            assert!(child.element().kind() == SyntaxKind::LiteralInputItemNode);
286        }
287        (&child).write(stream);
288    }
289    stream.end_line();
290}
291
292/// Formats a [`LiteralHintsItem`](wdl_ast::v1::LiteralHintsItem).
293///
294/// # Panics
295///
296/// This will panic if the provided `element` is not a valid WDL v1.x
297/// literal hints item.
298pub fn format_literal_hints_item(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
299    let mut children = element.children().expect("literal hints item children");
300
301    let key = children.next().expect("literal hints item key");
302    assert!(key.element().kind() == SyntaxKind::Ident);
303    (&key).write(stream);
304
305    let colon = children.next().expect("literal hints item colon");
306    assert!(colon.element().kind() == SyntaxKind::Colon);
307    (&colon).write(stream);
308    stream.end_word();
309
310    let value = children.next().expect("literal hints item value");
311    (&value).write(stream);
312}
313
314/// Formats a [`LiteralHints`](wdl_ast::v1::LiteralHints).
315///
316/// # Panics
317///
318/// This will panic if the provided `element` is not a valid WDL v1.x
319/// literal hints.
320pub fn format_literal_hints(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
321    let mut children = element.children().expect("literal hints children");
322
323    let hints_keyword = children.next().expect("literal hints keyword");
324    assert!(hints_keyword.element().kind() == SyntaxKind::HintsKeyword);
325    (&hints_keyword).write(stream);
326    stream.end_word();
327
328    let open_brace = children.next().expect("literal hints open brace");
329    assert!(open_brace.element().kind() == SyntaxKind::OpenBrace);
330    (&open_brace).write(stream);
331    stream.increment_indent();
332
333    let mut items = Vec::new();
334    let mut commas = Vec::new();
335    let mut close_brace = None;
336
337    for child in children {
338        match child.element().kind() {
339            SyntaxKind::LiteralHintsItemNode => items.push(child),
340            SyntaxKind::Comma => commas.push(child),
341            SyntaxKind::CloseBrace => close_brace = Some(child),
342            _ => panic!("unexpected literal hints child"),
343        }
344    }
345
346    let mut commas = commas.iter();
347    for item in items {
348        (&item).write(stream);
349        if let Some(comma) = commas.next() {
350            (comma).write(stream);
351        } else {
352            stream.push_literal(",".to_string(), SyntaxKind::Comma);
353        }
354        stream.end_line();
355    }
356
357    stream.decrement_indent();
358    (&close_brace.expect("literal hints close brace")).write(stream);
359}
360
361/// Formats a [`LiteralOutputItem`](wdl_ast::v1::LiteralOutputItem).
362///
363/// # Panics
364///
365/// This will panic if the provided `element` is not a valid WDL v1.x
366/// literal output item.
367pub fn format_literal_output_item(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
368    let mut children = element.children().expect("literal output item children");
369
370    for child in children.by_ref() {
371        if matches!(child.element().kind(), SyntaxKind::Ident | SyntaxKind::Dot) {
372            (&child).write(stream);
373        } else {
374            assert!(child.element().kind() == SyntaxKind::Colon);
375            (&child).write(stream);
376            stream.end_word();
377            break;
378        }
379    }
380
381    let value = children.next().expect("literal output item value");
382    (&value).write(stream);
383}
384
385/// Formats a [`LiteralOutput`](wdl_ast::v1::LiteralOutput).
386///
387/// # Panics
388///
389/// This will panic if the provided `element` is not a valid WDL v1.x
390/// literal output.
391pub fn format_literal_output(element: &FormatElement, stream: &mut TokenStream<PreToken>) {
392    let mut children = element.children().expect("literal output children");
393
394    let output_keyword = children.next().expect("literal output keyword");
395    assert!(output_keyword.element().kind() == SyntaxKind::OutputKeyword);
396    (&output_keyword).write(stream);
397    stream.end_word();
398
399    let open_brace = children.next().expect("literal output open brace");
400    assert!(open_brace.element().kind() == SyntaxKind::OpenBrace);
401    (&open_brace).write(stream);
402    stream.increment_indent();
403
404    let mut items = Vec::new();
405    let mut commas = Vec::new();
406    let mut close_brace = None;
407
408    for child in children {
409        match child.element().kind() {
410            SyntaxKind::LiteralOutputItemNode => items.push(child),
411            SyntaxKind::Comma => commas.push(child),
412            SyntaxKind::CloseBrace => close_brace = Some(child),
413            _ => panic!("unexpected literal output child"),
414        }
415    }
416
417    let mut commas = commas.iter();
418    for item in items {
419        (&item).write(stream);
420        if let Some(comma) = commas.next() {
421            (comma).write(stream);
422        } else {
423            stream.push_literal(",".to_string(), SyntaxKind::Comma);
424        }
425        stream.end_line();
426    }
427
428    stream.decrement_indent();
429    (&close_brace.expect("literal output close brace")).write(stream);
430}