cairo_lang_plugins/plugins/
external_attributes_validation.rs1use 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}