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