use cairo_lang_defs::plugin::{MacroPlugin, MacroPluginMetadata, PluginDiagnostic, PluginResult};
use cairo_lang_filesystem::ids::SmolStrId;
use cairo_lang_syntax::attribute::structured::{AttributeArgVariant, AttributeStructurize};
use cairo_lang_syntax::node::helpers::{GetIdentifier, QueryAttrs};
use cairo_lang_syntax::node::{TypedSyntaxNode, ast};
use itertools::Itertools;
use salsa::Database;
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct ExternalAttributesValidationPlugin;
pub const DOC_ATTR: &str = "doc";
const HIDDEN_ATTR: &str = "hidden";
pub const HIDDEN_ATTR_SYNTAX: &str = "#[doc(hidden)]";
const GROUP_ATTR: &str = "group";
const GROUP_ATTR_SYNTAX: &str = "#[doc(group: \"group name\")]";
impl MacroPlugin for ExternalAttributesValidationPlugin {
fn generate_code<'db>(
&self,
db: &'db dyn Database,
item_ast: ast::ModuleItem<'db>,
_metadata: &MacroPluginMetadata<'_>,
) -> PluginResult<'db> {
match get_diagnostics(db, &item_ast) {
Some(diagnostics) => {
PluginResult { code: None, remove_original_item: false, diagnostics }
}
None => PluginResult::default(),
}
}
fn declared_attributes<'db>(&self, db: &'db dyn Database) -> Vec<SmolStrId<'db>> {
vec![SmolStrId::from(db, DOC_ATTR)]
}
}
fn get_diagnostics<'a, Item: QueryAttrs<'a>>(
db: &'a dyn Database,
item: &Item,
) -> Option<Vec<PluginDiagnostic<'a>>> {
let mut diagnostics: Vec<PluginDiagnostic<'_>> = Vec::new();
item.query_attr(db, DOC_ATTR).for_each(|attr| {
let args = attr.clone().structurize(db).args;
if args.is_empty() {
diagnostics.push(PluginDiagnostic::error(
attr.stable_ptr(db),
format!("Expected arguments. Supported args: {HIDDEN_ATTR}, {GROUP_ATTR}."),
));
return;
}
args.iter().for_each(|arg| match &arg.variant {
AttributeArgVariant::Unnamed(value) => {
let ast::Expr::Path(path) = value else {
diagnostics.push(PluginDiagnostic::error(
value.stable_ptr(db),
format!("Expected identifier. Supported identifiers: {HIDDEN_ATTR}."),
));
return;
};
let Ok(ast::PathSegment::Simple(segment)) =
path.segments(db).elements(db).exactly_one()
else {
diagnostics.push(PluginDiagnostic::error(
path.stable_ptr(db),
format!(
"Wrong type of argument. Currently only {HIDDEN_ATTR_SYNTAX} is \
supported."
),
));
return;
};
if segment.identifier(db).long(db) != HIDDEN_ATTR {
diagnostics.push(PluginDiagnostic::error(
path.stable_ptr(db),
format!(
"Wrong type of argument. Currently only: {HIDDEN_ATTR_SYNTAX}, \
{GROUP_ATTR_SYNTAX} are supported."
),
));
}
}
AttributeArgVariant::Named { name, value } => match value {
ast::Expr::String(_) => {
if name.text.long(db) != GROUP_ATTR {
diagnostics.push(PluginDiagnostic::error(
arg.arg.stable_ptr(db),
format!(
"This argument is not supported. Supported args: {HIDDEN_ATTR}, \
{GROUP_ATTR}."
),
));
}
}
_ => {
diagnostics.push(PluginDiagnostic::error(
value.stable_ptr(db),
format!(
"Wrong type of argument. Currently only {GROUP_ATTR_SYNTAX} is \
supported."
),
));
}
},
_ => diagnostics.push(PluginDiagnostic::error(
arg.arg.stable_ptr(db),
format!(
"This argument is not supported. Supported args: {HIDDEN_ATTR}, {GROUP_ATTR}."
),
)),
});
});
if diagnostics.is_empty() { None } else { Some(diagnostics) }
}