Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support scarb packages_filter #51

Merged
merged 5 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 132 additions & 111 deletions crates/cairo-lint-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use anyhow::{anyhow, Result};
use cairo_lang_compiler::db::RootDatabase;
use cairo_lang_compiler::project::{update_crate_root, update_crate_roots_from_project_config};
use cairo_lang_defs::db::DefsGroup;
use cairo_lang_diagnostics::DiagnosticEntry;
use cairo_lang_diagnostics::{DiagnosticEntry, Maybe};
use cairo_lang_filesystem::db::{init_dev_corelib, FilesGroup, CORELIB_CRATE_NAME};
use cairo_lang_filesystem::ids::{CrateLongId, FileId};
use cairo_lang_semantic::db::SemanticGroup;
Expand All @@ -24,7 +24,7 @@ use cairo_lint_core::fix::{apply_import_fixes, collect_unused_imports, fix_seman
use cairo_lint_core::plugin::cairo_lint_plugin_suite;
use clap::Parser;
use helpers::*;
use scarb_metadata::MetadataCommand;
use scarb_metadata::{MetadataCommand, PackageMetadata, TargetMetadata};
use scarb_ui::args::{PackagesFilter, VerbositySpec};
use scarb_ui::components::Status;
use scarb_ui::{OutputFormat, Ui};
Expand Down Expand Up @@ -74,130 +74,151 @@ fn main_inner(ui: &Ui, args: Args) -> Result<()> {
let corelib_id = &corelib.id;
// Corelib path
let corelib = Into::<PathBuf>::into(corelib.manifest_path.parent().as_ref().unwrap()).join("src");
// Remove the compilation units that are not requested by the user. If none is specified will lint
// them all. The test target is a special case and will never be linted unless specified with the
// `--test` flag
let compilation_units = metadata.compilation_units.into_iter().filter(|compilation_unit| {
(args.target_names.is_empty() && compilation_unit.target.kind != targets::TEST)
|| (args.target_names.contains(&compilation_unit.target.kind))
|| (args.test && compilation_unit.target.kind == targets::TEST)
});
// Let's lint everything requested
for compilation_unit in compilation_units {
// Get the current package metadata
let package = metadata.packages.iter().find(|package| package.id == compilation_unit.package).unwrap();
// Print that we're checking this package.
ui.print(Status::new("Checking", &package.name));
// Create our db
let mut db = RootDatabase::builder()
.with_plugin_suite(get_default_plugin_suite())
.with_plugin_suite(test_plugin_suite())
.with_plugin_suite(cairo_lint_plugin_suite())
.with_plugin_suite(starknet_plugin_suite())
.with_cfg(to_cairo_cfg(&compilation_unit.cfg))
.build()?;
// Setup the corelib
init_dev_corelib(db.upcast_mut(), corelib.clone());
// Convert the package edition to a cairo edition. If not specified or not known it will return an
// error.
let edition = to_cairo_edition(
package.edition.as_ref().ok_or(anyhow!("No edition found for package {}", package.name))?,
)?;
// Get the package path.
let package_path = package.root.clone().into();
// Build the config for this package.
let config = build_project_config(
&compilation_unit,
corelib_id,
corelib.clone(),
package_path,
edition,
&package.version,
)?;
update_crate_roots_from_project_config(&mut db, &config);
if let Some(corelib) = &config.corelib {
update_crate_root(&mut db, &config, CORELIB_CRATE_NAME.into(), corelib.clone());
}
let crate_id =
Upcast::<dyn FilesGroup>::upcast(&db).intern_crate(CrateLongId::Real(SmolStr::new(&package.name)));
// Get all the diagnostics
let mut diags = Vec::new();
// Filter the packages that are requested by the user. The test target is a special case and will
// never be linted unless specified with the `--test` flag

for module_id in &*db.crate_modules(crate_id) {
diags.push(db.module_semantic_diagnostics(*module_id).unwrap());
}
let matched = args.packages_filter.match_many(&metadata)?;

let renderer = Renderer::styled();
// Let's lint everything requested
for package in matched {
// Get the current package metadata
let compilation_units = if args.test {
let tests_targets = find_testable_targets(&package);
metadata
.compilation_units
.iter()
.filter(|compilation_unit| {
compilation_unit.package == package.id || tests_targets.contains(&&compilation_unit.target)
})
.collect::<Vec<_>>()
} else {
vec![
metadata
.compilation_units
.iter()
.find(|compilation_unit| compilation_unit.package == package.id)
.unwrap(),
]
};
for compilation_unit in compilation_units {
// Print that we're checking this package.
ui.print(Status::new("Checking", &compilation_unit.target.name));
// Create our db
let mut db = RootDatabase::builder()
.with_plugin_suite(get_default_plugin_suite())
.with_plugin_suite(test_plugin_suite())
.with_plugin_suite(cairo_lint_plugin_suite())
.with_plugin_suite(starknet_plugin_suite())
.with_cfg(to_cairo_cfg(&compilation_unit.cfg))
.build()?;
// Setup the corelib
init_dev_corelib(db.upcast_mut(), corelib.clone());
// Convert the package edition to a cairo edition. If not specified or not known it will return an
// error.
let edition = to_cairo_edition(
package.edition.as_ref().ok_or(anyhow!("No edition found for package {}", package.name))?,
)?;
// Get the package path.
let package_path = package.root.clone().into();
// Build the config for this package.
let config = build_project_config(
compilation_unit,
corelib_id,
corelib.clone(),
package_path,
edition,
&package.version,
)?;
update_crate_roots_from_project_config(&mut db, &config);
if let Some(corelib) = &config.corelib {
update_crate_root(&mut db, &config, CORELIB_CRATE_NAME.into(), corelib.clone());
}
let crate_id =
Upcast::<dyn FilesGroup>::upcast(&db).intern_crate(CrateLongId::Real(SmolStr::new(&package.name)));
// Get all the diagnostics
let mut diags = Vec::new();

let diagnostics = diags
.iter()
.flat_map(|diags| {
let all_diags = diags.get_all();
all_diags.iter().for_each(|diag| ui.print(format_diagnostic(diag, &db, &renderer)));
all_diags
})
.collect::<Vec<_>>();
for module_id in &*db.crate_modules(crate_id) {
if let Maybe::Ok(module_diags) = db.module_semantic_diagnostics(*module_id) {
diags.push(module_diags);
}
}

if args.fix {
// Handling unused imports separately as we need to run pre-analysis on the diagnostics.
// to handle complex cases.
let unused_imports: HashMap<FileId, HashMap<SyntaxNode, ImportFix>> =
collect_unused_imports(&db, &diagnostics);
let mut fixes = HashMap::new();
unused_imports.keys().for_each(|file_id| {
let file_fixes: Vec<Fix> = apply_import_fixes(&db, unused_imports.get(file_id).unwrap());
fixes.insert(*file_id, file_fixes);
});
let renderer = Renderer::styled();

let diags_without_imports = diagnostics
let diagnostics = diags
.iter()
.filter(|diag| !matches!(diag.kind, SemanticDiagnosticKind::UnusedImport(_)))
.flat_map(|diags| {
let all_diags = diags.get_all();
all_diags.iter().for_each(|diag| ui.print(format_diagnostic(diag, &db, &renderer)));
all_diags
})
.collect::<Vec<_>>();

for diag in diags_without_imports {
if let Some((fix_node, fix)) = fix_semantic_diagnostic(&db, diag) {
let location = diag.location(db.upcast());
fixes
.entry(location.file_id)
.or_insert_with(Vec::new)
.push(Fix { span: fix_node.span(db.upcast()), suggestion: fix });
if args.fix {
// Handling unused imports separately as we need to run pre-analysis on the diagnostics.
// to handle complex cases.
let unused_imports: HashMap<FileId, HashMap<SyntaxNode, ImportFix>> =
collect_unused_imports(&db, &diagnostics);
let mut fixes = HashMap::new();
unused_imports.keys().for_each(|file_id| {
let file_fixes: Vec<Fix> = apply_import_fixes(&db, unused_imports.get(file_id).unwrap());
fixes.insert(*file_id, file_fixes);
});

let diags_without_imports = diagnostics
.iter()
.filter(|diag| !matches!(diag.kind, SemanticDiagnosticKind::UnusedImport(_)))
.collect::<Vec<_>>();

for diag in diags_without_imports {
if let Some((fix_node, fix)) = fix_semantic_diagnostic(&db, diag) {
let location = diag.location(db.upcast());
fixes
.entry(location.file_id)
.or_insert_with(Vec::new)
.push(Fix { span: fix_node.span(db.upcast()), suggestion: fix });
}
}
}
for (file_id, mut fixes) in fixes.into_iter() {
ui.print(Status::new("Fixing", &file_id.file_name(db.upcast())));
fixes.sort_by_key(|fix| Reverse(fix.span.start));
let mut fixable_diagnostics = Vec::with_capacity(fixes.len());
if fixes.len() <= 1 {
fixable_diagnostics = fixes;
} else {
for i in 0..fixes.len() - 1 {
let first = fixes[i].span;
let second = fixes[i + 1].span;
if first.start >= second.end {
fixable_diagnostics.push(fixes[i].clone());
if i == fixes.len() - 1 {
fixable_diagnostics.push(fixes[i + 1].clone());
for (file_id, mut fixes) in fixes.into_iter() {
ui.print(Status::new("Fixing", &file_id.file_name(db.upcast())));
fixes.sort_by_key(|fix| Reverse(fix.span.start));
let mut fixable_diagnostics = Vec::with_capacity(fixes.len());
if fixes.len() <= 1 {
fixable_diagnostics = fixes;
} else {
for i in 0..fixes.len() - 1 {
let first = fixes[i].span;
let second = fixes[i + 1].span;
if first.start >= second.end {
fixable_diagnostics.push(fixes[i].clone());
if i == fixes.len() - 1 {
fixable_diagnostics.push(fixes[i + 1].clone());
}
}
}
}
let mut files: HashMap<FileId, String> = HashMap::default();
files.insert(
file_id,
db.file_content(file_id)
.ok_or(anyhow!("{} not found", file_id.file_name(db.upcast())))?
.to_string(),
);
for fix in fixable_diagnostics {
// Can't fail we just set the file value.
files
.entry(file_id)
.and_modify(|file| file.replace_range(fix.span.to_str_range(), &fix.suggestion));
}
std::fs::write(file_id.full_path(db.upcast()), files.get(&file_id).unwrap())?
}
let mut files: HashMap<FileId, String> = HashMap::default();
files.insert(
file_id,
db.file_content(file_id)
.ok_or(anyhow!("{} not found", file_id.file_name(db.upcast())))?
.to_string(),
);
for fix in fixable_diagnostics {
// Can't fail we just set the file value.
files
.entry(file_id)
.and_modify(|file| file.replace_range(fix.span.to_str_range(), &fix.suggestion));
}
std::fs::write(file_id.full_path(db.upcast()), files.get(&file_id).unwrap())?
}
}
}

Ok(())
}

fn find_testable_targets(package: &PackageMetadata) -> Vec<&TargetMetadata> {
package.targets.iter().filter(|target| target.kind == "test").collect()
}
2 changes: 1 addition & 1 deletion crates/cairo-lint-core/src/fix/import_fixes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ fn remove_specific_items(db: &RootDatabase, node: &SyntaxNode, items_to_remove:
let mut items: Vec<_> = children.iter().map(|child| child.get_text(db).trim().to_string()).collect();
items.retain(|item| !items_to_remove.contains(&item.to_string()));

let text = if items.len() == 1 { items[0].to_string() } else { format!("{{ {} }}", items.join(", ")) };
let text = if items.len() == 1 { items[0].to_string() } else { format!("{{{}}}", items.join(", ")) };

vec![Fix { span: node.span(db), suggestion: text }]
}
Expand Down
Loading