use std::any::Any;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use cairo_lang_defs::db::DefsGroup;
use cairo_lang_defs::ids::{InlineMacroExprPluginLongId, MacroPluginLongId};
use cairo_lang_filesystem::db::{CrateConfiguration, CrateSettings, FilesGroup};
use cairo_lang_filesystem::ids::{CrateId, CrateLongId, Directory};
use cairo_lang_semantic::db::SemanticGroup;
use cairo_lang_semantic::ids::AnalyzerPluginLongId;
use itertools::{Itertools, chain};
use serde::Serialize;
use crate::config::Config;
use crate::lang::db::AnalysisDatabase;
use crate::lang::proc_macros::plugins::{InlineProcMacroPlugin, ProcMacroPlugin};
use crate::project::builtin_plugins::BuiltinPlugin;
use crate::project::{ConfigsRegistry, extract_custom_file_stems};
use crate::toolchain::scarb::ScarbToolchain;
pub fn inspect_analyzed_crates(
db: &AnalysisDatabase,
config: &Config,
configs_registry: &ConfigsRegistry,
scarb_toolchain: &ScarbToolchain,
) -> String {
let crates = db
.crates()
.iter()
.filter_map(|id| CrateView::for_crate(db, config, configs_registry, scarb_toolchain, *id))
.sorted()
.map(|cr| serde_json::to_string_pretty(&cr))
.collect::<Result<Vec<_>, _>>()
.unwrap_or_else(|err| vec![format!(r#"{{"error": {}}}"#, err.to_string())])
.join("\n\n");
format!("# Analyzed Crates\n---\n```json\n{crates}\n```")
}
#[derive(Debug, Serialize, PartialEq, Eq)]
struct CrateView {
name: String,
source_paths: Vec<PathBuf>,
settings: CrateSettings,
linter_configuration: LinterConfiguration,
plugins: Plugins,
}
#[allow(clippy::non_canonical_partial_ord_impl)]
impl PartialOrd for CrateView {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some((&self.name, &self.source_paths).cmp(&(&other.name, &other.source_paths)))
}
}
impl Ord for CrateView {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).expect("PartialOrd should not fail")
}
}
impl CrateView {
fn for_crate<'db>(
db: &'db AnalysisDatabase,
config: &Config,
configs_registry: &ConfigsRegistry,
scarb_toolchain: &ScarbToolchain,
crate_id: CrateId<'db>,
) -> Option<Self> {
let CrateLongId::Real { name, .. } = crate_id.long(db) else {
return None;
};
let Some(CrateConfiguration { root: Directory::Real(root), settings, .. }) =
db.crate_config(crate_id)
else {
return None;
};
let source_paths = extract_custom_file_stems(db, crate_id)
.map(|stems| stems.iter().map(|stem| root.join(format!("{stem}.cairo"))).collect_vec())
.unwrap_or_else(|| vec![root.join("lib.cairo")]);
let linter_configuration =
LinterConfiguration::for_crate(config, configs_registry, scarb_toolchain, root);
let plugins = Plugins::for_crate(db, crate_id);
Some(Self {
name: name.to_string(db),
source_paths,
settings: settings.clone(),
linter_configuration,
plugins,
})
}
}
#[derive(Debug, Serialize, PartialEq, Eq)]
enum LinterConfiguration {
Off,
#[serde(untagged)]
On(HashMap<String, bool>),
}
impl LinterConfiguration {
fn for_crate(
config: &Config,
configs_registry: &ConfigsRegistry,
scarb_toolchain: &ScarbToolchain,
root_path: &Path,
) -> Self {
if !config.enable_linter || scarb_toolchain.is_from_scarb_cache(root_path) {
return Self::Off;
}
Self::On(
configs_registry.config_for_file(root_path).map_or_else(Default::default, |config| {
config.lint.clone().into_iter().collect::<HashMap<_, _>>()
}),
)
}
}
#[derive(Debug, Serialize, PartialEq, Eq)]
struct Plugins {
#[serde(skip_serializing_if = "Vec::is_empty")]
builtin_plugins: Vec<BuiltinPlugin>,
#[serde(skip_serializing_if = "Vec::is_empty")]
proc_macros: Vec<String>,
}
impl Plugins {
fn for_crate<'db>(db: &'db AnalysisDatabase, crate_id: CrateId<'db>) -> Self {
let analyzer_plugins = db.crate_analyzer_plugins(crate_id);
let macro_plugins = db.crate_macro_plugins(crate_id);
let inline_macros = db.crate_inline_macro_plugins(crate_id);
let inline_plugins: Vec<_> = inline_macros
.iter()
.unique_by(|(_, id)| **id)
.collect();
let plugins = chain!(
analyzer_plugins.iter().filter_map(|id| Plugin::try_from(id.long(db).clone()).ok()),
macro_plugins.iter().filter_map(|id| Plugin::try_from(id.long(db).clone()).ok()),
inline_plugins.iter().filter_map(|(_, id)| Plugin::try_from(id.long(db).clone()).ok())
);
let mut builtin_plugins = vec![];
let mut proc_macros = vec![];
for plugin in plugins {
match plugin {
Plugin::Builtin(builtin_plugin) => builtin_plugins.push(builtin_plugin),
Plugin::ProcMacro(proc_macro) => proc_macros.push(proc_macro),
}
}
let builtin_plugins = builtin_plugins.into_iter().unique().sorted().collect_vec();
let proc_macros = proc_macros
.into_iter()
.flat_map(|m| m.source_packages)
.sorted()
.unique()
.collect_vec();
Self { builtin_plugins, proc_macros }
}
}
enum Plugin {
Builtin(BuiltinPlugin),
ProcMacro(ProcMacro),
}
#[derive(Debug, Serialize, PartialEq, Eq, PartialOrd, Ord)]
struct ProcMacro {
source_packages: Vec<String>,
}
impl TryFrom<MacroPluginLongId> for Plugin {
type Error = ();
fn try_from(id: MacroPluginLongId) -> Result<Self, Self::Error> {
let plugin = &*id.0;
if let Some(builtin_plugin) = BuiltinPlugin::try_from_compiler_macro_plugin(plugin) {
return Ok(Self::Builtin(builtin_plugin));
}
let plugin_any: &dyn Any = plugin;
let plugin: &ProcMacroPlugin = plugin_any.downcast_ref().ok_or(())?;
Ok(Self::ProcMacro(ProcMacro { source_packages: plugin.source_packages().to_vec() }))
}
}
impl TryFrom<InlineMacroExprPluginLongId> for Plugin {
type Error = ();
fn try_from(id: InlineMacroExprPluginLongId) -> Result<Self, Self::Error> {
let plugin = &*id.0;
if let Some(builtin_plugin) = BuiltinPlugin::try_from_compiler_inline_macro_plugin(plugin) {
return Ok(Self::Builtin(builtin_plugin));
}
let plugin_any: &dyn Any = plugin;
let plugin: &InlineProcMacroPlugin = plugin_any.downcast_ref().ok_or(())?;
Ok(Self::ProcMacro(ProcMacro { source_packages: plugin.source_packages().to_vec() }))
}
}
impl TryFrom<AnalyzerPluginLongId> for Plugin {
type Error = ();
fn try_from(plugin: AnalyzerPluginLongId) -> Result<Self, Self::Error> {
BuiltinPlugin::try_from_compiler_analyzer_plugin(&*plugin.0).map(Self::Builtin).ok_or(())
}
}