1use proc_macro2::TokenStream;
2use quote::quote;
3
4#[derive(Clone, Debug)]
5pub struct Scope {
6 before: Vec<syn::Stmt>,
7 inner: Option<Box<Scope>>,
8 after: Vec<syn::Stmt>,
9}
10
11impl Scope {
12 pub fn empty() -> Self {
13 Self {
14 before: vec![],
15 inner: None,
16 after: vec![],
17 }
18 }
19
20 pub fn new(before: &[syn::Stmt], after: &[syn::Stmt]) -> Self {
21 Self {
22 before: Vec::from(before),
23 inner: None,
24 after: Vec::from(after),
25 }
26 }
27
28 pub fn push_mut(&mut self, before: &[syn::Stmt], after: &[syn::Stmt]) {
29 if let Some(inner) = self.inner.as_mut() {
30 inner.push_mut(before, after);
31 } else {
32 self.inner = Some(Box::new(Scope::new(before, after)));
33 }
34 }
35
36 pub fn quote_with(&self, stmts: &[syn::Stmt]) -> TokenStream {
37 let Scope {
38 before,
39 inner,
40 after,
41 } = &self;
42
43 if let Some(inner) = inner.as_ref() {
44 let inner = inner.quote_with(stmts);
45
46 quote! {
47 #(#before)*
48 {
49 #inner
50 }
51 #(#after)*
52 }
53 } else {
54 quote! {
55 #(#before)*
56 {
57 #(#stmts)*
58 }
59 #(#after)*
60 }
61 }
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use quote::quote;
68 use syn::parse_quote;
69
70 use super::*;
71
72 fn assert_eq_string(exp: impl ToString, act: impl ToString) {
73 assert_eq!(exp.to_string(), act.to_string());
74 }
75
76 #[test]
77 fn quote_empty_scope() {
78 let scope = Scope::empty();
79
80 let act = scope.quote_with(&[]);
81
82 assert_eq_string(quote!({}), act);
83 }
84
85 #[test]
86 fn quote_empty_scope_with_items() {
87 let scope = Scope::empty();
88
89 let act = scope.quote_with(&[
90 parse_quote!(let x = 1;),
91 parse_quote!(assert_eq!(x, 1);),
92 ]);
93
94 assert_eq_string(
95 quote!({
96 let x = 1;
97 assert_eq!(x, 1);
98 }),
99 act,
100 );
101 }
102
103 #[test]
104 fn quote_non_empty_scope() {
105 let scope = Scope::new(
106 &[parse_quote!(let x = 1;)],
107 &[parse_quote!(assert_eq!(x, 1);)],
108 );
109
110 let act = scope.quote_with(&[]);
111
112 assert_eq_string(
113 quote!(
114 let x = 1;
115 {
116
117 }
118 assert_eq!(x, 1);
119 ),
120 act,
121 );
122 }
123
124 #[test]
125 fn quote_non_empty_scope_with_items() {
126 let scope = Scope::new(
127 &[parse_quote!(let x = 1;)],
128 &[parse_quote!(assert_eq!(x, 1);)],
129 );
130
131 let act = scope.quote_with(&[parse_quote!(assert!(true);)]);
132
133 assert_eq_string(
134 quote!(
135 let x = 1;
136 {
137 assert!(true);
138 }
139 assert_eq!(x, 1);
140 ),
141 act,
142 );
143 }
144
145 #[test]
146 fn push_empty_scope() {
147 let mut scope = Scope::empty();
148 scope.push_mut(&[], &[]);
149
150 let act = scope.quote_with(&[]);
151
152 assert_eq_string(quote!({ {} }), act);
153 }
154
155 #[test]
156 fn push_non_empty_scope() {
157 let mut scope = Scope::new(
158 &[parse_quote!(let x = 1;)],
159 &[parse_quote!(assert_eq!(x, 1);)],
160 );
161
162 scope.push_mut(
163 &[parse_quote!(assert!(true);)],
164 &[parse_quote!(assert!(false);)],
165 );
166
167 let act = scope.quote_with(&[parse_quote!(assert!(true);)]);
168
169 assert_eq_string(
170 quote!(
171 let x = 1;
172 {
173 assert!(true);
174 {
175 assert!(true);
176 }
177 assert!(false);
178 }
179 assert_eq!(x, 1);
180 ),
181 act,
182 );
183 }
184}