Skip to main content

cairo_lang_plugins/plugins/
external_attributes_validation.rs

1use cairo_lang_defs::plugin::{MacroPlugin, MacroPluginMetadata, PluginDiagnostic, PluginResult};
2use cairo_lang_syntax::attribute::structured::{AttributeArgVariant, AttributeStructurize};
3use cairo_lang_syntax::node::db::SyntaxGroup;
4use cairo_lang_syntax::node::helpers::{GetIdentifier, QueryAttrs};
5use cairo_lang_syntax::node::{TypedSyntaxNode, ast};
6use itertools::Itertools;
7
8#[derive(Debug, Default)]
9#[non_exhaustive]
10pub struct ExternalAttributesValidationPlugin;
11
12pub const DOC_ATTR: &str = "doc";
13const HIDDEN_ATTR: &str = "hidden";
14pub const HIDDEN_ATTR_SYNTAX: &str = "#[doc(hidden)]";
15const GROUP_ATTR: &str = "group";
16const GROUP_ATTR_SYNTAX: &str = "#[doc(group: \"group name\")]";
17
18impl MacroPlugin for ExternalAttributesValidationPlugin {
19    fn generate_code(
20        &self,
21        db: &dyn SyntaxGroup,
22        item_ast: ast::ModuleItem,
23        _metadata: &MacroPluginMetadata<'_>,
24    ) -> PluginResult {
25        match get_diagnostics(db, &item_ast) {
26            Some(diagnostics) => {
27                PluginResult { code: None, remove_original_item: false, diagnostics }
28            }
29            None => PluginResult::default(),
30        }
31    }
32
33    fn declared_attributes(&self) -> Vec<String> {
34        vec![DOC_ATTR.to_string()]
35    }
36}
37
38fn get_diagnostics<Item: QueryAttrs>(
39    db: &dyn SyntaxGroup,
40    item: &Item,
41) -> Option<Vec<PluginDiagnostic>> {
42    let mut diagnostics: Vec<PluginDiagnostic> = Vec::new();
43    item.query_attr(db, DOC_ATTR).for_each(|attr| {
44        let args = attr.clone().structurize(db).args;
45        if args.is_empty() {
46            diagnostics.push(PluginDiagnostic::error(
47                attr.stable_ptr(db),
48                format!("Expected arguments. Supported args: {HIDDEN_ATTR}, {GROUP_ATTR}."),
49            ));
50            return;
51        }
52        args.iter().for_each(|arg| match &arg.variant {
53            AttributeArgVariant::Unnamed(value) => {
54                let ast::Expr::Path(path) = value else {
55                    diagnostics.push(PluginDiagnostic::error(
56                        value.stable_ptr(db),
57                        format!("Expected identifier. Supported identifiers: {HIDDEN_ATTR}."),
58                    ));
59                    return;
60                };
61                let Some([ast::PathSegment::Simple(segment)]) =
62                    path.segments(db).elements(db).collect_array()
63                else {
64                    diagnostics.push(PluginDiagnostic::error(
65                        path.stable_ptr(db),
66                        format!(
67                            "Wrong type of argument. Currently only {HIDDEN_ATTR_SYNTAX} is \
68                             supported."
69                        ),
70                    ));
71                    return;
72                };
73                if segment.identifier(db) != HIDDEN_ATTR {
74                    diagnostics.push(PluginDiagnostic::error(
75                        path.stable_ptr(db),
76                        format!(
77                            "Wrong type of argument. Currently only: {HIDDEN_ATTR_SYNTAX}, \
78                             {GROUP_ATTR_SYNTAX}  are supported."
79                        ),
80                    ));
81                }
82            }
83            AttributeArgVariant::Named { name, value } => match value {
84                ast::Expr::String(_) => {
85                    if name.text != GROUP_ATTR {
86                        diagnostics.push(PluginDiagnostic::error(
87                            arg.arg.stable_ptr(db),
88                            format!(
89                                "This argument is not supported. Supported args: {HIDDEN_ATTR}, \
90                                 {GROUP_ATTR}."
91                            ),
92                        ));
93                    }
94                }
95                _ => {
96                    diagnostics.push(PluginDiagnostic::error(
97                        value.stable_ptr(db),
98                        format!(
99                            "Wrong type of argument. Currently only {GROUP_ATTR_SYNTAX} is \
100                             supported."
101                        ),
102                    ));
103                }
104            },
105            _ => diagnostics.push(PluginDiagnostic::error(
106                arg.arg.stable_ptr(db),
107                format!(
108                    "This argument is not supported. Supported args: {HIDDEN_ATTR}, {GROUP_ATTR}."
109                ),
110            )),
111        });
112    });
113    if diagnostics.is_empty() { None } else { Some(diagnostics) }
114}