deno_bindgen2_common/rust/
attr.rs

1use crate::rust::util::*;
2use crate::rust::Item;
3
4/* -------------------------------------------------------------------------- */
5
6// MARK: marker
7
8/// `markers` here denote live attributes that get converted to inert attributes
9/// (as doc attributes) the live version is used by the proc macro library,
10/// while the inert versions (the doc attributes ones) are used by the cli to
11/// control code generation. it does this by using the parser implementation
12/// here to read the marker info
13#[derive(Clone, Debug, PartialEq)]
14pub enum Marker {
15    DenoBindgen, // marks a deno bindgen item. automatically inserted by the item macro
16    NonBlocking, /* marks a function as non-blocking */
17
18                 /* [!TODO] support for translating member visibility https://www.typescriptlang.org/docs/handbook/2/classes.html#member-visibility
19                  * interpret visibility of rust functions and interpolate as class visibility
20                  * useful for implementing internal methods */
21}
22
23// TODO: move from doc attributes to inert attributes
24// support custom inert attributes rfc
25// https://github.com/rust-lang/rust/issues/66079
26
27#[cfg(feature = "macro")]
28impl Marker {
29    pub fn deno_bindgen(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
30        let input = TokenStream::from(input);
31        let mut item: Item = match syn::parse2(input.clone()) {
32            Ok(item) => item,
33            Err(err) => return err.to_compile_error().into(),
34        };
35        item.transform();
36        quote! {
37            #[cfg_attr(not(deno_bindgen), doc = "deno_bindgen")]
38            #input
39            #item
40        }
41        .into()
42    }
43
44    pub fn non_blocking(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
45        let input = TokenStream::from(input);
46        quote! {
47            #[cfg_attr(not(deno_bindgen), doc = "deno_bindgen_non_blocking")]
48            #input
49        }
50        .into()
51    }
52}
53
54/* -------------------------------------------------------------------------- */
55
56// MARK: meta
57
58/// a document attribute meta in the form `doc = "value"`
59#[derive(Clone, Debug, PartialEq)]
60pub struct Meta {
61    pub lit_str: LitStr,
62}
63
64impl TryFrom<&Meta> for Marker {
65    fn try_from(value: &Meta) -> Result<Self> {
66        match value.lit_str.value().as_str() {
67            "deno_bindgen" => Ok(Self::DenoBindgen),
68            "deno_bindgen_non_blocking" => Ok(Self::NonBlocking),
69            _ => Err(Error::new(
70                value.lit_str.span(),
71                "unknown value. expected one of `deno_bindgen`, `deno_bindgen_non_blocking`, `deno_bindgen_constructor`"
72            )),
73        }
74    }
75
76    type Error = Error;
77}
78
79impl Parse for Meta {
80    fn parse(input: ParseStream) -> Result<Self> {
81        let key = input.parse::<Ident>()?;
82
83        if input.is_empty() {
84            let key_str = key.to_string();
85            match key_str.as_str() {
86                "non_blocking" => {
87                    let lit_str = LitStr::new(
88                        format!("deno_bindgen_{key_str}").as_str(),
89                        Span::mixed_site(),
90                    );
91                    return Ok(Self { lit_str });
92                },
93                _ => (),
94            }
95        }
96
97        input.parse::<Token![=]>()?;
98        if key.to_string().as_str() == "doc" {
99            let lit_str = input.parse()?;
100
101            // emit error if there are remaining tokens in the stream
102            if !input.is_empty() {
103                Err(input.error("unknown token"))
104            } else {
105                Ok(Self { lit_str })
106            }
107        } else {
108            Err(Error::new(key.span(), "expected `doc` key"))
109        }
110    }
111}
112
113/* -------------------------------------------------------------------------- */
114
115// MARK: attribute
116
117#[derive(Clone, Debug, Default, PartialEq)]
118pub struct Attribute {
119    pub markers: Vec<Marker>,
120    pub meta:    Vec<Meta>,
121    // pub doc: Vec<String>, // [!TODO] support for documentation in code, with auto-generated docs
122    // by the tool
123}
124
125impl Attribute {
126    /// checks if this attribute contains the `deno_bindgen` marker
127    pub fn has_deno_bindgen(&self) -> bool {
128        self.markers
129            .iter()
130            .find(|marker| match marker {
131                Marker::DenoBindgen => true,
132                _ => false,
133            })
134            .is_some()
135    }
136
137    /// checks if this attribute contains the `non_blocking` marker
138    pub fn has_non_blocking(&self) -> bool {
139        self.markers
140            .iter()
141            .find(|marker| match marker {
142                Marker::NonBlocking => true,
143                _ => false,
144            })
145            .is_some()
146    }
147}
148
149impl Attribute {
150    pub fn parse_outer(&mut self, input: ParseStream) -> Result<()> {
151        while input.peek(Token![#]) {
152            input.parse::<Token![#]>()?;
153            if input.peek(Token![!]) {
154                return Err(Error::new(
155                    input.span(),
156                    "attempted to parse inner attribute in a parser for outer attributes",
157                ));
158            }
159
160            let content;
161            bracketed!(content in input);
162
163            let fork = content.fork();
164            if let Ok(meta) = fork.parse::<Meta>() {
165                content.advance_to(&fork);
166                if let Ok(marker) = Marker::try_from(&meta) {
167                    self.markers.push(marker);
168                } else {
169                    self.meta.push(meta);
170                }
171            } else {
172                // content should have been exhausted by doc_meta parser
173                content.parse::<syn::Meta>()?;
174            }
175        }
176
177        Ok(())
178    }
179
180    pub fn parse_inner(&mut self, input: ParseStream) -> Result<()> {
181        while input.peek(Token![#]) && input.peek2(Token![!]) {
182            input.parse::<Token![#]>()?;
183            input.parse::<Token![!]>()?;
184            let content;
185            bracketed!(content in input);
186
187            let fork = content.fork();
188            if let Ok(meta) = fork.parse::<Meta>() {
189                content.advance_to(&fork);
190                self.meta.push(meta);
191            } else {
192                content.parse::<syn::Meta>()?;
193            }
194        }
195
196        Ok(())
197    }
198}
199
200/* -------------------------------------------------------------------------- */
201
202// MARK: parse tests
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    impl Parse for Attribute {
209        /// Used for debugging only. Cannot mix inner or outer attributes with
210        /// each other in a single parse run
211        fn parse(input: ParseStream) -> Result<Self> {
212            let mut attr = Self::default();
213
214            let ahead = input.lookahead1();
215            if ahead.peek(Token![#]) {
216                if input.peek2(Token![!]) {
217                    attr.parse_inner(input)?;
218                } else {
219                    attr.parse_outer(input)?;
220                }
221            } else {
222                return Err(ahead.error());
223            }
224
225            Ok(attr)
226        }
227    }
228
229    #[test]
230    fn test_attr() {
231        dbg_quote!(Attribute,
232            #[some_attr]
233            #[doc = "some unknown value"]
234            #[another_attr]
235        );
236    }
237
238    #[test]
239    #[should_panic]
240    fn test_mix_attr() {
241        dbg_quote!(Attribute,
242            #[outer]
243            #![innter]
244        );
245    }
246
247    #[test]
248    fn test_marker() {
249        dbg_quote!(Attribute,
250            #[doc = "deno_bindgen_constructor"]
251            #[doc = "deno_bindgen"]
252        );
253    }
254
255    #[test]
256    fn test_cfg_attr() {
257        dbg_quote!(Attribute,
258            #[cfg_attr(not(deno_bindgen), doc = "deno_bindgen_constructor")]
259        );
260    }
261
262    #[test]
263    fn test_live_attr() {
264        dbg_quote!(Attribute,
265            #[constructor]
266        );
267    }
268}