near_syn/
lib.rs

1//! Augments `syn`'s AST with helper methods to deal with Near SDK definitions.
2//! Additionally, provides function to deal with Rust syntax.
3#![deny(warnings)]
4#![warn(missing_docs)]
5
6use std::io::{self, Write};
7
8use contract::NearItemTrait;
9use syn::{
10    Attribute, FnArg, ImplItem, ImplItemMethod, ItemEnum, ItemImpl, ItemStruct, Lit, Meta,
11    MetaList, MetaNameValue, NestedMeta, Path, PathArguments, Type, Visibility,
12};
13
14pub mod contract;
15pub mod md;
16pub mod ts;
17
18/// Defines standard attributes found in the NEAR SDK.
19///
20/// This `trait` is intended to extend `syn::ItemImpl` to support
21/// NEAR definitions.
22///
23/// This trait _should_ not be applied to the contract `struct`,
24/// since the `struct` is not part of the public interface of the NEAR contract.
25///
26/// An overview of the `near_bindgen` attribute macro can be found in
27/// <https://www.near-sdk.io/contract-structure/near-bindgen>.
28pub trait NearImpl {
29    /// Returns the trait name this `impl` implements, if any.
30    fn get_trait_name(&self) -> Option<String>;
31
32    /// Returns the struct this `impl` implements.
33    /// Only `Type::Path` are supported.
34    fn get_impl_name(&self) -> Option<String>;
35
36    /// Returns whether the given `self` implementation has any exported method.
37    ///
38    /// For more info on public method in the NEAR SDK,
39    /// see <https://www.near-sdk.io/contract-interface/public-methods>.
40    fn exported_methods(&self) -> Vec<&ImplItemMethod>;
41
42    /// Returns the exported methods if `self` `is_bindgen`.
43    /// In this case, the inner `Vec` should contain elements.
44    /// Otherwise, it returns `None`.
45    fn bindgen_methods(&self) -> Option<Vec<&ImplItemMethod>>;
46
47    /// Join the attributes of this impl with its corresponding trait definition.
48    /// Useful to gather documentation attributes.
49    fn join_attrs(&self, item_trait: Option<&NearItemTrait>) -> Vec<Attribute>;
50}
51
52impl NearImpl for ItemImpl {
53    fn get_trait_name(&self) -> Option<String> {
54        if let Some((_excl, trait_path, _for)) = &self.trait_ {
55            let trait_name = join_path(trait_path);
56            Some(trait_name)
57        } else {
58            None
59        }
60    }
61
62    fn get_impl_name(&self) -> Option<String> {
63        if let Type::Path(type_path) = &*self.self_ty {
64            Some(join_path(&type_path.path))
65        } else {
66            None
67        }
68    }
69
70    fn exported_methods(&self) -> Vec<&ImplItemMethod> {
71        let mut methods = Vec::new();
72        for impl_item in self.items.iter() {
73            if let ImplItem::Method(method) = impl_item {
74                if method.is_exported(self) {
75                    methods.push(method);
76                }
77            }
78        }
79
80        methods
81    }
82
83    fn bindgen_methods(&self) -> Option<Vec<&ImplItemMethod>> {
84        let methods = self.exported_methods();
85        if self.is_bindgen() && methods.len() > 0 {
86            return Some(methods);
87        }
88
89        None
90    }
91
92    fn join_attrs(&self, item_trait: Option<&NearItemTrait>) -> Vec<Attribute> {
93        if let Some(base_trait) = item_trait {
94            let mut attrs = self.attrs.clone();
95            attrs.extend(base_trait.attrs.clone());
96            return attrs;
97        } else {
98            self.attrs.clone()
99        }
100    }
101}
102
103/// Defines standard attributes and helper methods used when exporting a contract.
104pub trait NearMethod {
105    /// Returns whether the given `self` method is declared as `pub`.
106    fn is_public(&self) -> bool;
107
108    /// Returns whether the given `self` method is declared as `mut`.
109    fn is_mut(&self) -> bool;
110
111    /// Returns whether the given `self` method is marked as `init`.
112    fn is_init(&self) -> bool;
113
114    /// Returns whether the given `self` method is marked as `payable`.
115    fn is_payable(&self) -> bool;
116
117    /// Returns whether the given `self` method is marked as `private`.
118    fn is_private(&self) -> bool;
119
120    /// Returns whether the given `self` method in `input` impl is being exported.
121    fn is_exported(&self, input: &ItemImpl) -> bool;
122
123    /// Join the attributes of this impl with its corresponding trait definition.
124    /// Useful to gather documentation attributes.
125    fn join_attrs(&self, item_trait: Option<&NearItemTrait>) -> Vec<Attribute>;
126}
127
128impl NearMethod for ImplItemMethod {
129    fn is_public(self: &ImplItemMethod) -> bool {
130        match self.vis {
131            Visibility::Public(_) => true,
132            _ => false,
133        }
134    }
135
136    fn is_mut(&self) -> bool {
137        if let Some(FnArg::Receiver(r)) = self.sig.inputs.iter().next() {
138            r.mutability.is_some()
139        } else {
140            false
141        }
142    }
143
144    fn is_init(&self) -> bool {
145        has_attr(&self.attrs, "init")
146    }
147
148    fn is_payable(&self) -> bool {
149        has_attr(&self.attrs, "payable")
150    }
151
152    fn is_private(self: &ImplItemMethod) -> bool {
153        has_attr(&self.attrs, "private")
154    }
155
156    fn is_exported(&self, input: &ItemImpl) -> bool {
157        (self.is_public() || input.trait_.is_some()) && !self.is_private()
158    }
159
160    fn join_attrs(&self, item_trait: Option<&NearItemTrait>) -> Vec<Attribute> {
161        let name = self.sig.ident.to_string();
162        if let Some(base_trait) = item_trait {
163            if let Some(base_method) = base_trait.get(&name) {
164                let mut attrs = self.attrs.clone();
165                attrs.extend(base_method.attrs.clone());
166                return attrs;
167            }
168            self.attrs.clone()
169        } else {
170            self.attrs.clone()
171        }
172    }
173}
174
175///
176pub trait NearBindgen {
177    /// Returns whether the given `self` implementation is marked as `near_bindgen`.
178    /// This should be an indication to further process this `impl` item.
179    ///
180    /// ### Examples
181    ///
182    /// The following `impl` item is marked as `near_bindgen`.
183    ///
184    /// ```compile_fail
185    /// #[near_bindgen]
186    /// impl Counter {
187    ///     // methods...
188    /// }
189    /// ```
190    fn is_bindgen(&self) -> bool;
191}
192
193impl<I: NearAttributable> NearBindgen for I {
194    fn is_bindgen(&self) -> bool {
195        has_attr(&self.attrs(), "near_bindgen")
196    }
197}
198
199/// Defines methods to deal with serde's declarations in `struct`s or `enum`s.
200pub trait NearSerde {
201    /// Returns whether the given `self` item derives `serde::Serialize`.
202    fn is_serialize(&self) -> bool;
203
204    /// Returns whether the given `self` item derives `serde::Deserialize`.
205    fn is_deserialize(&self) -> bool;
206
207    /// Returns whether the given `self` item derives either `serde::Serialize` or `serde::Deserialize`.
208    fn is_serde(&self) -> bool;
209}
210
211impl<I: NearAttributable> NearSerde for I {
212    fn is_serialize(&self) -> bool {
213        derives(&self.attrs(), "Serialize")
214    }
215
216    fn is_deserialize(&self) -> bool {
217        derives(&self.attrs(), "Deserialize")
218    }
219
220    fn is_serde(&self) -> bool {
221        self.is_serialize() || self.is_deserialize()
222    }
223}
224
225/// Any Rust item, *e.g.*, `struct` or `enum` to which attributes can attached to.
226pub trait NearAttributable {
227    /// The attributes of this item.
228    fn attrs(&self) -> &Vec<Attribute>;
229}
230
231impl NearAttributable for ItemImpl {
232    fn attrs(&self) -> &Vec<Attribute> {
233        &self.attrs
234    }
235}
236
237impl NearAttributable for ItemStruct {
238    fn attrs(&self) -> &Vec<Attribute> {
239        &self.attrs
240    }
241}
242
243impl NearAttributable for ItemEnum {
244    fn attrs(&self) -> &Vec<Attribute> {
245        &self.attrs
246    }
247}
248
249/// Returns `true` if `attrs` contain `attr_name`.
250/// Returns `false` otherwise.
251fn has_attr(attrs: &Vec<Attribute>, attr_name: &str) -> bool {
252    for attr in attrs {
253        if is_ident(&attr.path, attr_name) {
254            return true;
255        }
256    }
257    false
258}
259
260/// Returns `true` if any of the attributes under item derive from `macro_name`.
261/// Returns `false` otherwise.
262fn derives(attrs: &Vec<Attribute>, macro_name: &str) -> bool {
263    for attr in attrs {
264        if attr.path.is_ident("derive") {
265            if let Ok(Meta::List(MetaList { nested, .. })) = attr.parse_meta() {
266                for elem in nested {
267                    if let NestedMeta::Meta(Meta::Path(path)) = elem {
268                        if is_ident(&path, macro_name) {
269                            return true;
270                        }
271                    }
272                }
273            }
274        }
275    }
276    false
277}
278
279fn is_ident(path: &Path, ident: &str) -> bool {
280    fn last_segment_is_ident(path: &Path, ident: &str) -> bool {
281        let segments = &path.segments;
282        let len = segments.len();
283        len >= 2
284            && segments[len - 1].arguments == PathArguments::None
285            && segments[len - 1].ident.to_string() == ident
286    }
287
288    path.is_ident(ident) || last_segment_is_ident(path, ident)
289}
290
291/// Joins segments of a path by `::`.
292///
293/// ## Example
294///
295/// ```
296/// //use near_syn::join_path;
297/// //use quote::quote;
298///
299/// //let path = syn::parse2(quote! { A::B::C }).unwrap();
300/// //assert_eq!(join_path(&path), "A::B::C");
301/// ```
302fn join_path(path: &syn::Path) -> String {
303    path.segments
304        .iter()
305        .map(|seg| seg.ident.to_string())
306        .collect::<Vec<String>>()
307        .join("::")
308}
309
310///
311pub fn get_docs(attrs: &Vec<Attribute>) -> Vec<String> {
312    let mut docs = Vec::new();
313    for attr in attrs {
314        if attr.path.is_ident("doc") {
315            if let Ok(Meta::NameValue(MetaNameValue {
316                lit: Lit::Str(lit), ..
317            })) = attr.parse_meta()
318            {
319                docs.push(lit.value());
320            } else {
321                panic!("not expected");
322            }
323        }
324    }
325
326    docs
327}
328
329/// Writes Rust `doc` comments to `file`.
330/// Each line of `doc` is prefixed with `prefix`.
331/// See <https://doc.rust-lang.org/rustdoc/the-doc-attribute.html>.
332pub fn write_docs<W: Write, F: Fn(String) -> String>(
333    file: &mut W,
334    attrs: &Vec<Attribute>,
335    mapf: F,
336) -> io::Result<()> {
337    for attr in attrs {
338        if attr.path.is_ident("doc") {
339            if let Ok(Meta::NameValue(MetaNameValue {
340                lit: Lit::Str(lit), ..
341            })) = attr.parse_meta()
342            {
343                writeln!(file, "{}", mapf(lit.value()))?;
344            } else {
345                panic!("not expected");
346            }
347        }
348    }
349
350    Ok(())
351}
352
353#[cfg(test)]
354mod tests {
355
356    mod has_attr {
357
358        use proc_macro2::TokenStream;
359        use quote::quote;
360        use syn::{Attribute, ItemImpl};
361
362        use crate::has_attr;
363
364        fn impl_attrs(tokens: TokenStream) -> Vec<Attribute> {
365            let item = syn::parse2::<ItemImpl>(tokens).unwrap();
366            item.attrs
367        }
368
369        #[test]
370        fn it_should_return_true_when_attr_is_defined() {
371            let attrs = impl_attrs(quote! {
372                #[near_bindgen]
373                impl Contract { }
374            });
375            assert!(has_attr(&attrs, "near_bindgen"));
376        }
377
378        #[test]
379        fn it_should_return_false_when_attr_is_not_defined() {
380            let attrs = impl_attrs(quote! {
381                #[not_near_bindgen]
382                #[::near_bindgen]
383                #[near_bindgen::not]
384                impl Contract { }
385            });
386            assert!(!has_attr(&attrs, "near_bindgen"));
387        }
388
389        #[test]
390        fn it_should_return_true_when_attr_is_defined_multiple_times() {
391            let attrs = impl_attrs(quote! {
392                #[near_bindgen]
393                #[near_bindgen]
394                #[maybe_near_bindgen]
395                impl Contract { }
396            });
397            assert!(has_attr(&attrs, "near_bindgen"));
398        }
399
400        #[test]
401        fn it_should_return_true_when_attr_is_defined_using_qualifiers() {
402            let attrs = impl_attrs(quote! {
403                #[near_sdk::near_bindgen]
404                #[::near_sdk::near_bindgen]
405                impl Contract { }
406            });
407            assert!(has_attr(&attrs, "near_bindgen"));
408        }
409    }
410
411    mod derives {
412
413        use proc_macro2::TokenStream;
414        use quote::quote;
415        use syn::{Attribute, ItemStruct};
416
417        use crate::derives;
418
419        fn struct_attrs(tokens: TokenStream) -> Vec<Attribute> {
420            let item = syn::parse2::<ItemStruct>(tokens).unwrap();
421            item.attrs
422        }
423
424        #[test]
425        fn it_should_return_true_when_struct_is_derived() {
426            let attrs = struct_attrs(quote! {
427                #[derive(Serialize)]
428                struct Data { }
429            });
430            assert!(derives(&attrs, "Serialize"));
431        }
432
433        #[test]
434        fn it_should_return_true_when_struct_is_derived_with_qualifiers() {
435            let attrs = struct_attrs(quote! {
436                #[derive(::near_sdk::serde::Serialize)]
437                struct Data { }
438            });
439            assert!(derives(&attrs, "Serialize"));
440        }
441
442        #[test]
443        fn it_should_return_false_when_struct_is_not_derived() {
444            let attrs = struct_attrs(quote! {
445                #[derive()]
446                #[derive(::Serialize)]
447                #[derive=Serialize]
448                #[derive(Serialize=1)]
449                struct Data { }
450            });
451            assert!(!derives(&attrs, "Serialize"));
452        }
453    }
454}