js_component_bindgen/
source.rs

1use std::fmt::{self, Write};
2use std::ops::Deref;
3
4pub use wit_parser;
5
6#[derive(Default)]
7pub struct Source {
8    s: String,
9    indent: usize,
10}
11
12impl Source {
13    pub fn push_str(&mut self, src: &str) {
14        let lines = src.lines().collect::<Vec<_>>();
15        for (i, line) in lines.iter().enumerate() {
16            let trimmed = line.trim();
17            if trimmed.starts_with('}') && self.s.ends_with("  ") {
18                self.s.pop();
19                self.s.pop();
20            }
21            self.s.push_str(if lines.len() == 1 {
22                line
23            } else {
24                line.trim_start()
25            });
26            if trimmed.ends_with('{') {
27                self.indent += 1;
28            }
29            if trimmed.starts_with('}') {
30                // Note that a `saturating_sub` is used here to prevent a panic
31                // here in the case of invalid code being generated in debug
32                // mode. It's typically easier to debug those issues through
33                // looking at the source code rather than getting a panic.
34                self.indent = self.indent.saturating_sub(1);
35            }
36            if i != lines.len() - 1 || src.ends_with('\n') {
37                self.newline();
38            }
39        }
40    }
41
42    pub fn prepend_str(&mut self, src: &str) {
43        // Infer the indent at start: it's the difference between the size of the first line, and
44        // its size if trimmed at the left. That raw difference is in number of spaces, not in
45        // units of indent; since each indent is 2 spaces, divide by 2 at the end.
46        let indent = self
47            .s
48            .lines()
49            .next()
50            .map_or(0, |line| (line.len() - line.trim_start().len()) / 2);
51        let mut new_start = Source {
52            s: String::new(),
53            indent,
54        };
55        new_start.push_str(src);
56        self.s = new_start.s + &self.s;
57    }
58
59    pub fn indent(&mut self, amt: usize) {
60        self.indent += amt;
61    }
62
63    pub fn deindent(&mut self, amt: usize) {
64        self.indent -= amt;
65    }
66
67    fn newline(&mut self) {
68        self.s.push('\n');
69        for _ in 0..self.indent {
70            self.s.push_str("  ");
71        }
72    }
73
74    pub fn as_mut_string(&mut self) -> &mut String {
75        &mut self.s
76    }
77}
78
79impl Write for Source {
80    fn write_str(&mut self, s: &str) -> fmt::Result {
81        self.push_str(s);
82        Ok(())
83    }
84}
85
86impl Deref for Source {
87    type Target = str;
88    fn deref(&self) -> &str {
89        &self.s
90    }
91}
92
93impl From<Source> for String {
94    fn from(s: Source) -> String {
95        s.s
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::Source;
102
103    #[test]
104    fn simple_append() {
105        let mut s = Source::default();
106        s.push_str("x");
107        assert_eq!(s.s, "x");
108        s.push_str("y");
109        assert_eq!(s.s, "xy");
110        s.push_str("z ");
111        assert_eq!(s.s, "xyz ");
112        s.push_str(" a ");
113        assert_eq!(s.s, "xyz  a ");
114        s.push_str("\na");
115        assert_eq!(s.s, "xyz  a \na");
116    }
117
118    #[test]
119    fn newline_remap() {
120        let mut s = Source::default();
121        s.push_str("function() {\n");
122        s.push_str("y\n");
123        s.push_str("}\n");
124        assert_eq!(s.s, "function() {\n  y\n}\n");
125    }
126
127    #[test]
128    fn if_else() {
129        let mut s = Source::default();
130        s.push_str("if() {\n");
131        s.push_str("y\n");
132        s.push_str("} else if () {\n");
133        s.push_str("z\n");
134        s.push_str("}\n");
135        assert_eq!(s.s, "if() {\n  y\n} else if () {\n  z\n}\n");
136    }
137
138    #[test]
139    fn trim_ws() {
140        let mut s = Source::default();
141        s.push_str(
142            "function() {
143                x
144        }",
145        );
146        assert_eq!(s.s, "function() {\n  x\n}");
147    }
148}