cairo_lint_core/
plugin.rs

1use cairo_lang_defs::ids::{LanguageElementId, ModuleId};
2use cairo_lang_defs::plugin::PluginDiagnostic;
3use cairo_lang_filesystem::ids::FileLongId;
4use cairo_lang_semantic::db::SemanticGroup;
5use cairo_lang_semantic::plugin::{AnalyzerPlugin, PluginSuite};
6use cairo_lang_syntax::node::db::SyntaxGroup;
7use cairo_lang_syntax::node::helpers::QueryAttrs;
8use cairo_lang_syntax::node::SyntaxNode;
9use cairo_lang_utils::LookupIntern;
10
11use crate::context::{
12    get_all_checking_functions, get_name_for_diagnostic_message, get_unique_allowed_names,
13};
14
15pub fn cairo_lint_plugin_suite() -> PluginSuite {
16    let mut suite = PluginSuite::default();
17    suite.add_analyzer_plugin::<CairoLint>();
18    suite
19}
20
21pub fn cairo_lint_allow_plugin_suite() -> PluginSuite {
22    let mut suite = PluginSuite::default();
23    suite.add_analyzer_plugin::<CairoLintAllow>();
24    suite
25}
26
27#[derive(Debug, Default)]
28pub struct CairoLint {
29    include_compiler_generated_files: bool,
30}
31
32impl CairoLint {
33    pub fn new(include_compiler_generated_files: bool) -> Self {
34        Self {
35            include_compiler_generated_files,
36        }
37    }
38}
39
40impl AnalyzerPlugin for CairoLint {
41    fn declared_allows(&self) -> Vec<String> {
42        get_unique_allowed_names()
43            .iter()
44            .map(ToString::to_string)
45            .collect()
46    }
47
48    fn diagnostics(&self, db: &dyn SemanticGroup, module_id: ModuleId) -> Vec<PluginDiagnostic> {
49        let mut diags = Vec::new();
50        let Ok(items) = db.module_items(module_id) else {
51            return diags;
52        };
53        for item in &*items {
54            // Skip compiler generated files. By default it checks whether the item is inside the virtual or external file.
55            if !self.include_compiler_generated_files
56                && matches!(
57                    item.stable_location(db.upcast())
58                        .file_id(db.upcast())
59                        .lookup_intern(db),
60                    FileLongId::Virtual(_) | FileLongId::External(_)
61                )
62            {
63                continue;
64            }
65
66            let checking_functions = get_all_checking_functions();
67
68            for checking_function in checking_functions {
69                checking_function(db, item, &mut diags);
70            }
71        }
72
73        diags
74            .into_iter()
75            .filter(|diag| {
76                let node = diag.stable_ptr.lookup(db.upcast());
77                let allowed_name = get_name_for_diagnostic_message(&diag.message).unwrap();
78                !node_has_ascendants_with_allow_name_attr(db.upcast(), node, allowed_name)
79            })
80            .collect()
81    }
82}
83
84/// Plugin with `declared_allows` matching these of [`CairoLint`] that does not emit diagnostics.
85/// Add it when `CairoLint` is not present to avoid compiler warnings on unsupported
86/// `allow` attribute arguments.
87#[derive(Debug, Default)]
88pub struct CairoLintAllow;
89
90impl AnalyzerPlugin for CairoLintAllow {
91    fn diagnostics(&self, _db: &dyn SemanticGroup, _module_id: ModuleId) -> Vec<PluginDiagnostic> {
92        Vec::new()
93    }
94
95    fn declared_allows(&self) -> Vec<String> {
96        get_unique_allowed_names()
97            .iter()
98            .map(ToString::to_string)
99            .collect()
100    }
101}
102
103fn node_has_ascendants_with_allow_name_attr(
104    db: &dyn SyntaxGroup,
105    node: SyntaxNode,
106    allowed_name: &'static str,
107) -> bool {
108    let mut current_node = node;
109    while let Some(node) = current_node.parent() {
110        if node.has_attr_with_arg(db, "allow", allowed_name) {
111            return true;
112        }
113        current_node = node;
114    }
115    false
116}