Skip to main content

wdl_format/
element.rs

1//! Elements used during formatting.
2
3use std::iter::Peekable;
4
5use nonempty::NonEmpty;
6use wdl_ast::Element;
7use wdl_ast::Node;
8use wdl_ast::SyntaxTokenExt;
9
10pub mod node;
11
12/// An iterator that asserts that all items have been consumed when dropped.
13pub struct AssertConsumedIter<I: Iterator>(Peekable<I>);
14
15impl<I> AssertConsumedIter<I>
16where
17    I: Iterator,
18{
19    /// Creates a new [`AssertConsumedIter`].
20    pub fn new(iter: I) -> Self {
21        Self(iter.peekable())
22    }
23}
24
25impl<I> Iterator for AssertConsumedIter<I>
26where
27    I: Iterator,
28{
29    type Item = I::Item;
30
31    fn next(&mut self) -> Option<Self::Item> {
32        self.0.next()
33    }
34}
35
36impl<I> Drop for AssertConsumedIter<I>
37where
38    I: Iterator,
39{
40    fn drop(&mut self) {
41        assert!(
42            self.0.peek().is_none(),
43            "not all iterator items were consumed!"
44        );
45    }
46}
47
48/// A formattable element.
49#[derive(Clone, Debug)]
50pub struct FormatElement {
51    /// The inner element.
52    element: Element,
53
54    /// Children as format elements.
55    children: Option<NonEmpty<Box<FormatElement>>>,
56}
57
58impl FormatElement {
59    /// Creates a new [`FormatElement`].
60    pub fn new(element: Element, children: Option<NonEmpty<Box<FormatElement>>>) -> Self {
61        Self { element, children }
62    }
63
64    /// Gets the inner element.
65    pub fn element(&self) -> &Element {
66        &self.element
67    }
68
69    /// Gets the children for this node.
70    pub fn children(&self) -> Option<AssertConsumedIter<impl Iterator<Item = &FormatElement>>> {
71        self.children
72            .as_ref()
73            // NOTE: we wrap the iterator in an [`AssertConsumedIter`] to ensure
74            // that no children are ever forgotten to be formatted (they must be
75            // explicitly consumed and dropped).
76            .map(|children| AssertConsumedIter::new(children.iter().map(|c| c.as_ref())))
77    }
78
79    /// Returns `true` if the underlying syntax for `element` contains a comment
80    /// token (in case of a node) or has inline or preceding trivia (in case of
81    /// a token).
82    ///
83    /// `FormatElement` collation drops trivia, so this peeks at the raw syntax
84    /// instead.
85    pub fn has_comment(&self) -> bool {
86        if let Some(node) = self.element().as_node() {
87            return node
88                .inner()
89                .children_with_tokens()
90                .any(|c| c.kind() == wdl_ast::SyntaxKind::Comment);
91        };
92        let token = self.element().as_token().expect("must be node or token");
93        token.inner().inline_comment().is_some()
94            || token
95                .inner()
96                .preceding_trivia()
97                .any(|t| t.kind() == wdl_ast::SyntaxKind::Comment)
98    }
99}
100
101/// An extension trait for formatting [`Element`]s.
102pub trait AstElementFormatExt {
103    /// Consumes `self` and returns the [`Element`] as a [`FormatElement`].
104    fn into_format_element(self) -> FormatElement;
105}
106
107impl AstElementFormatExt for Element {
108    fn into_format_element(self) -> FormatElement
109    where
110        Self: Sized,
111    {
112        let children = match &self {
113            Element::Node(node) => collate(node),
114            Element::Token(_) => None,
115        };
116
117        FormatElement::new(self, children)
118    }
119}
120
121/// Collates the children of a particular node.
122///
123/// This function ignores trivia.
124fn collate(node: &Node) -> Option<NonEmpty<Box<FormatElement>>> {
125    let mut results = Vec::new();
126    let stream = node.inner().children_with_tokens().filter_map(|syntax| {
127        if syntax.kind().is_trivia() {
128            None
129        } else {
130            Some(Element::cast(syntax))
131        }
132    });
133
134    for element in stream {
135        let children = match element {
136            Element::Node(ref node) => collate(node),
137            Element::Token(_) => None,
138        };
139
140        results.push(Box::new(FormatElement { element, children }));
141    }
142
143    if !results.is_empty() {
144        let mut results = results.into_iter();
145        // SAFETY: we just checked to ensure that `results` wasn't empty, so
146        // this will always unwrap.
147        let mut children = NonEmpty::new(results.next().unwrap());
148        children.extend(results);
149        Some(children)
150    } else {
151        None
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use wdl_ast::Document;
158    use wdl_ast::Node;
159    use wdl_ast::SyntaxKind;
160
161    use crate::element::node::AstNodeFormatExt;
162
163    #[test]
164    fn smoke() {
165        let (document, diagnostics) = Document::parse(
166            "## WDL
167version 1.2  # This is a comment attached to the version.
168
169# This is a comment attached to the task keyword.
170task foo # This is an inline comment on the task ident.
171{
172
173} # This is an inline comment on the task close brace.
174
175# This is a comment attached to the workflow keyword.
176workflow bar # This is an inline comment on the workflow ident.
177{
178  # This is attached to the call keyword.
179  call foo {}
180} # This is an inline comment on the workflow close brace.",
181            None,
182        );
183
184        assert!(diagnostics.is_empty());
185        let document = document.ast().into_v1().unwrap();
186
187        let format_element = Node::Ast(document).into_format_element();
188        let mut children = format_element.children().unwrap();
189
190        // Version statement.
191
192        let version = children.next().expect("version statement element");
193        assert_eq!(
194            version.element().inner().kind(),
195            SyntaxKind::VersionStatementNode
196        );
197
198        let mut version_children = version.children().unwrap();
199        assert_eq!(
200            version_children.next().unwrap().element().kind(),
201            SyntaxKind::VersionKeyword
202        );
203        assert_eq!(
204            version_children.next().unwrap().element().kind(),
205            SyntaxKind::Version
206        );
207
208        // Task Definition.
209
210        let task = children.next().expect("task element");
211        assert_eq!(
212            task.element().inner().kind(),
213            SyntaxKind::TaskDefinitionNode
214        );
215
216        // Children.
217
218        let mut task_children = task.children().unwrap();
219        assert_eq!(
220            task_children.next().unwrap().element().kind(),
221            SyntaxKind::TaskKeyword
222        );
223
224        let ident = task_children.next().unwrap();
225        assert_eq!(ident.element().kind(), SyntaxKind::Ident);
226
227        assert_eq!(
228            task_children.next().unwrap().element().kind(),
229            SyntaxKind::OpenBrace
230        );
231        assert_eq!(
232            task_children.next().unwrap().element().kind(),
233            SyntaxKind::CloseBrace
234        );
235
236        assert!(task_children.next().is_none());
237
238        // Workflow Definition.
239
240        let workflow = children.next().expect("workflow element");
241        assert_eq!(
242            workflow.element().inner().kind(),
243            SyntaxKind::WorkflowDefinitionNode
244        );
245
246        // Children.
247
248        let mut workflow_children = workflow.children().unwrap();
249
250        assert_eq!(
251            workflow_children.next().unwrap().element().kind(),
252            SyntaxKind::WorkflowKeyword
253        );
254
255        let ident = workflow_children.next().unwrap();
256        assert_eq!(ident.element().kind(), SyntaxKind::Ident);
257
258        assert_eq!(
259            workflow_children.next().unwrap().element().kind(),
260            SyntaxKind::OpenBrace
261        );
262
263        let call = workflow_children.next().unwrap();
264        assert_eq!(call.element().kind(), SyntaxKind::CallStatementNode);
265
266        assert_eq!(
267            workflow_children.next().unwrap().element().kind(),
268            SyntaxKind::CloseBrace
269        );
270
271        assert!(workflow_children.next().is_none());
272    }
273
274    #[test]
275    #[should_panic]
276    fn unconsumed_children_nodes_panic() {
277        let (document, diagnostics) = Document::parse(
278            "## WDL
279version 1.2  # This is a comment attached to the version.
280
281# This is a comment attached to the task keyword.
282task foo # This is an inline comment on the task ident.
283{
284
285} # This is an inline comment on the task close brace.",
286            None,
287        );
288
289        assert!(diagnostics.is_empty());
290        let document = document.ast().into_v1().unwrap();
291
292        let format_element = Node::Ast(document).into_format_element();
293        fn inner(format_element: &crate::element::FormatElement) {
294            let mut _children = format_element.children().unwrap();
295        }
296        inner(&format_element);
297    }
298}