cairo_lang_plugins/plugins/
external_attributes_validation.rs

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