Skip to main content

sc_lint_attributes/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use sc_lint_directives::AttributeInput;
4use syn::Result;
5
6#[cfg(test)]
7use sc_lint_directives::Directive;
8
9fn expand_sc_lint(args: TokenStream2, item: TokenStream2) -> Result<TokenStream2> {
10    let _parsed = syn::parse2::<AttributeInput>(args)?;
11    Ok(item)
12}
13
14#[proc_macro_attribute]
15pub fn sc_lint(args: TokenStream, item: TokenStream) -> TokenStream {
16    let item_ts: TokenStream2 = item.clone().into();
17    let args_ts: TokenStream2 = args.into();
18    match expand_sc_lint(args_ts, item_ts) {
19        Ok(expanded) => TokenStream::from(expanded),
20        Err(error) => TokenStream::from(error.to_compile_error()),
21    }
22}
23
24#[cfg(test)]
25mod tests {
26    use super::AttributeInput;
27    use super::Directive;
28    use super::expand_sc_lint;
29    use quote::quote;
30
31    #[test]
32    fn parses_boundary_allow_rule() {
33        let parsed: AttributeInput =
34            syn::parse2(quote!(boundary.allow("cycle.type_method_self_loop"))).unwrap();
35        assert_eq!(
36            parsed.directives,
37            vec![Directive::Allow(vec![
38                "cycle.type_method_self_loop".to_string()
39            ])]
40        );
41    }
42
43    #[test]
44    fn parses_boundary_internal_only() {
45        let parsed: AttributeInput = syn::parse2(quote!(boundary.internal_only)).unwrap();
46        assert_eq!(parsed.directives, vec![Directive::InternalOnly]);
47    }
48
49    #[test]
50    fn parses_boundary_forbid_external_impls() {
51        let parsed: AttributeInput = syn::parse2(quote!(boundary.forbid_external_impls)).unwrap();
52        assert_eq!(parsed.directives, vec![Directive::ForbidExternalImpls]);
53    }
54
55    #[test]
56    fn parses_multiple_directives() {
57        let parsed: AttributeInput = syn::parse2(quote!(
58            boundary.internal_only,
59            boundary.forbid_external_impls,
60            boundary.allow("cycle.type_method_self_loop")
61        ))
62        .unwrap();
63        assert_eq!(
64            parsed.directives,
65            vec![
66                Directive::InternalOnly,
67                Directive::ForbidExternalImpls,
68                Directive::Allow(vec!["cycle.type_method_self_loop".to_string()]),
69            ]
70        );
71    }
72
73    #[test]
74    fn rejects_unknown_boundary_directive() {
75        let error = syn::parse2::<AttributeInput>(quote!(boundary.unknown)).unwrap_err();
76        assert!(error.to_string().contains("unsupported boundary directive"));
77    }
78
79    #[test]
80    fn expansion_is_noop_for_supported_directives() {
81        let expanded = expand_sc_lint(
82            quote!(
83                boundary.internal_only,
84                boundary.forbid_external_impls,
85                boundary.allow("cycle.type_method_self_loop")
86            ),
87            quote!(
88                pub struct Example;
89            ),
90        )
91        .unwrap();
92        assert_eq!(
93            expanded.to_string(),
94            quote!(
95                pub struct Example;
96            )
97            .to_string()
98        );
99    }
100}