Skip to main content

cairo_lint/lang/
db.rs

1use anyhow::{Result, anyhow};
2use cairo_lang_compiler::{
3    db::validate_corelib,
4    project::{ProjectConfig, update_crate_roots_from_project_config},
5};
6use cairo_lang_defs::{
7    db::{defs_group_input, init_defs_group, init_external_files},
8    ids::{InlineMacroExprPluginLongId, MacroPluginLongId},
9};
10use cairo_lang_filesystem::flag::FlagsGroup;
11use cairo_lang_filesystem::{
12    cfg::CfgSet,
13    db::{FilesGroup, init_dev_corelib, init_files_group},
14    detect::detect_corelib,
15    flag::Flag,
16    ids::FlagLongId,
17};
18use cairo_lang_semantic::{
19    db::{init_semantic_group, semantic_group_input},
20    ids::AnalyzerPluginLongId,
21    inline_macros::get_default_plugin_suite,
22    plugin::PluginSuite,
23};
24
25use crate::plugin::cairo_lint_allow_plugin_suite;
26use salsa::Setter;
27
28#[salsa::db]
29#[derive(Clone)]
30pub struct LinterAnalysisDatabase {
31    storage: salsa::Storage<Self>,
32}
33
34impl LinterAnalysisDatabase {
35    pub fn builder() -> LinterAnalysisDatabaseBuilder {
36        LinterAnalysisDatabaseBuilder::new()
37    }
38
39    fn new(mut default_plugin_suite: PluginSuite) -> Self {
40        let mut res = Self {
41            storage: Default::default(),
42        };
43        init_files_group(&mut res);
44        init_defs_group(&mut res);
45        init_semantic_group(&mut res);
46        init_external_files(&mut res);
47
48        default_plugin_suite.add(cairo_lint_allow_plugin_suite());
49
50        defs_group_input(&res)
51            .set_default_macro_plugins(&mut res)
52            .to(Some(
53                default_plugin_suite
54                    .plugins
55                    .into_iter()
56                    .map(MacroPluginLongId)
57                    .collect(),
58            ));
59        defs_group_input(&res)
60            .set_default_inline_macro_plugins(&mut res)
61            .to(Some(
62                default_plugin_suite
63                    .inline_macro_plugins
64                    .into_iter()
65                    .map(|(name, value)| (name, InlineMacroExprPluginLongId(value)))
66                    .collect(),
67            ));
68        semantic_group_input(&res)
69            .set_default_analyzer_plugins(&mut res)
70            .to(Some(
71                default_plugin_suite
72                    .analyzer_plugins
73                    .into_iter()
74                    .map(AnalyzerPluginLongId)
75                    .collect(),
76            ));
77
78        res
79    }
80}
81
82impl salsa::Database for LinterAnalysisDatabase {}
83
84#[derive(Clone, Debug)]
85pub struct LinterAnalysisDatabaseBuilder {
86    default_plugin_suite: PluginSuite,
87    detect_corelib: bool,
88    auto_withdraw_gas: bool,
89    panic_backtrace: bool,
90    unsafe_panic: bool,
91    project_config: Option<Box<ProjectConfig>>,
92    cfg_set: Option<CfgSet>,
93}
94
95impl LinterAnalysisDatabaseBuilder {
96    fn new() -> Self {
97        Self {
98            default_plugin_suite: get_default_plugin_suite(),
99            detect_corelib: false,
100            auto_withdraw_gas: true,
101            panic_backtrace: false,
102            unsafe_panic: false,
103            project_config: None,
104            cfg_set: None,
105        }
106    }
107
108    pub fn with_default_plugin_suite(&mut self, suite: PluginSuite) -> &mut Self {
109        self.default_plugin_suite.add(suite);
110        self
111    }
112
113    pub fn clear_plugins(&mut self) -> &mut Self {
114        self.default_plugin_suite = get_default_plugin_suite();
115        self
116    }
117
118    pub fn detect_corelib(&mut self) -> &mut Self {
119        self.detect_corelib = true;
120        self
121    }
122
123    pub fn with_project_config(&mut self, config: ProjectConfig) -> &mut Self {
124        self.project_config = Some(Box::new(config));
125        self
126    }
127
128    pub fn with_cfg(&mut self, cfg_set: impl Into<CfgSet>) -> &mut Self {
129        self.cfg_set = Some(cfg_set.into());
130        self
131    }
132
133    pub fn skip_auto_withdraw_gas(&mut self) -> &mut Self {
134        self.auto_withdraw_gas = false;
135        self
136    }
137
138    pub fn with_panic_backtrace(&mut self) -> &mut Self {
139        self.panic_backtrace = true;
140        self
141    }
142
143    pub fn with_unsafe_panic(&mut self) -> &mut Self {
144        self.unsafe_panic = true;
145        self
146    }
147
148    pub fn build(&mut self) -> Result<LinterAnalysisDatabase> {
149        // NOTE: Order of operations matters here!
150        // Errors if something is not OK are very subtle, mostly this results in missing
151        // identifier diagnostics, or panics regarding lack of corelib items.
152
153        let mut db = LinterAnalysisDatabase::new(self.default_plugin_suite.clone());
154
155        if let Some(cfg_set) = &self.cfg_set {
156            db.use_cfg(cfg_set);
157        }
158
159        if self.detect_corelib {
160            let path =
161                detect_corelib().ok_or_else(|| anyhow!("Failed to find development corelib."))?;
162            init_dev_corelib(&mut db, path)
163        }
164
165        db.set_flag(
166            FlagLongId("add_withdraw_gas".to_string()),
167            Some(Flag::AddWithdrawGas(self.auto_withdraw_gas)),
168        );
169
170        db.set_flag(
171            FlagLongId("panic_backtrace".to_string()),
172            Some(Flag::PanicBacktrace(self.panic_backtrace)),
173        );
174
175        db.set_flag(
176            FlagLongId("unsafe_panic".to_string()),
177            Some(Flag::UnsafePanic(self.unsafe_panic)),
178        );
179
180        if let Some(config) = &self.project_config {
181            update_crate_roots_from_project_config(&mut db, config.as_ref());
182        }
183        validate_corelib(&db)?;
184
185        Ok(db)
186    }
187}