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::QueryAttrs;
5use cairo_lang_syntax::node::{Terminal, TypedSyntaxNode, ast};
6
7#[derive(Debug, Default)]
8#[non_exhaustive]
9pub struct ExternalAttributesValidationPlugin;
10
11const DOC_ATTR: &str = "doc";
12const HIDDEN_ATTR: &str = "hidden";
13
14impl MacroPlugin for ExternalAttributesValidationPlugin {
15    fn generate_code(
16        &self,
17        db: &dyn SyntaxGroup,
18        item_ast: ast::ModuleItem,
19        _metadata: &MacroPluginMetadata<'_>,
20    ) -> PluginResult {
21        match get_diagnostics(db, &item_ast) {
22            Some(diagnostics) => {
23                PluginResult { code: None, remove_original_item: false, diagnostics }
24            }
25            None => PluginResult::default(),
26        }
27    }
28
29    fn declared_attributes(&self) -> Vec<String> {
30        vec![DOC_ATTR.to_string()]
31    }
32}
33
34fn get_diagnostics<Item: QueryAttrs>(
35    db: &dyn SyntaxGroup,
36    item: &Item,
37) -> Option<Vec<PluginDiagnostic>> {
38    let mut diagnostics: Vec<PluginDiagnostic> = Vec::new();
39    item.query_attr(db, DOC_ATTR).into_iter().for_each(|attr| {
40        let args = attr.clone().structurize(db).args;
41        if args.is_empty() {
42            diagnostics.push(PluginDiagnostic::error(
43                attr.stable_ptr(),
44                format!("Expected arguments. Supported args: {}", HIDDEN_ATTR),
45            ));
46            return;
47        }
48        args.iter().for_each(|arg| match &arg.variant {
49            AttributeArgVariant::Unnamed(value) => {
50                let ast::Expr::Path(path) = value else {
51                    diagnostics.push(PluginDiagnostic::error(
52                        value,
53                        format!("Expected identifier. Supported identifiers: {}", HIDDEN_ATTR),
54                    ));
55                    return;
56                };
57                let [ast::PathSegment::Simple(segment)] = &path.elements(db)[..] else {
58                    diagnostics.push(PluginDiagnostic::error(
59                        path,
60                        "Wrong type of argument. Currently only #[doc(hidden)] is supported."
61                            .to_owned(),
62                    ));
63                    return;
64                };
65                if segment.ident(db).text(db) != HIDDEN_ATTR {
66                    diagnostics.push(PluginDiagnostic::error(
67                        path,
68                        "Wrong type of argument. Currently only #[doc(hidden)] is supported."
69                            .to_owned(),
70                    ));
71                }
72            }
73            _ => diagnostics.push(PluginDiagnostic::error(
74                &arg.arg,
75                format!("This argument is not supported. Supported args: {}", HIDDEN_ATTR),
76            )),
77        });
78    });
79    if diagnostics.is_empty() { None } else { Some(diagnostics) }
80}