diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index d99510ae8..bf9ae6d75 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -42,6 +42,9 @@ jobs: - name: Upgrade CairoLS to latest main commit run: cargo xtask upgrade cairols --rev $(git ls-remote --refs "https://github.com/software-mansion/cairols" main | awk '{print $1}') + - name: Upgrade Cairo-lint to latest main commit + run: cargo xtask upgrade cairolint --rev $(git ls-remote --refs "https://github.com/software-mansion/cairo-lint" main | awk '{print $1}') + - name: Rebuild xtasks after Cargo.toml changes run: cargo build -p xtask diff --git a/Cargo.lock b/Cargo.lock index 0d8c5af44..46a9fde88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,6 +77,16 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "annotate-snippets" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" +dependencies = [ + "anstyle", + "unicode-width 0.2.0", +] + [[package]] name = "anstream" version = "0.6.14" @@ -1240,6 +1250,26 @@ dependencies = [ "which", ] +[[package]] +name = "cairo-lint-core" +version = "0.1.0" +source = "git+https://github.com/software-mansion/cairo-lint?rev=b95a1949b932e89179c052efdbf4e21002ce6777#b95a1949b932e89179c052efdbf4e21002ce6777" +dependencies = [ + "annotate-snippets", + "anyhow", + "cairo-lang-compiler", + "cairo-lang-defs", + "cairo-lang-diagnostics", + "cairo-lang-filesystem", + "cairo-lang-semantic", + "cairo-lang-syntax", + "cairo-lang-test-plugin", + "cairo-lang-utils", + "if_chain", + "log", + "num-bigint", +] + [[package]] name = "cairo-toolchain-xtasks" version = "1.1.1" @@ -3490,6 +3520,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + [[package]] name = "ignore" version = "0.4.23" @@ -5096,6 +5132,7 @@ dependencies = [ "cairo-lang-syntax", "cairo-lang-test-plugin", "cairo-lang-utils", + "cairo-lint-core", "camino", "cargo_metadata", "clap", diff --git a/Cargo.toml b/Cargo.toml index 79281ecf7..82fc62c6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,7 @@ cairo-lang-test-plugin = "*" cairo-lang-test-runner = "*" cairo-lang-utils = { version = "*", features = ["env_logger"] } cairo-language-server = "*" +cairo-lint-core = "*" camino = { version = "1", features = ["serde1"] } cargo_metadata = ">=0.18" clap = { version = "4", features = ["derive", "env", "string"] } @@ -192,6 +193,7 @@ cairo-lang-test-runner = { git = "https://github.com/starkware-libs/cairo", rev cairo-lang-test-utils = { git = "https://github.com/starkware-libs/cairo", rev = "03944ce36c4b37ef954d7f462d23edce8669e692" } cairo-lang-utils = { git = "https://github.com/starkware-libs/cairo", rev = "03944ce36c4b37ef954d7f462d23edce8669e692" } cairo-language-server = { git = "https://github.com/software-mansion/cairols", rev = "6432886fea7564816078ed140434addf50bbaa23" } +cairo-lint-core = { git = "https://github.com/software-mansion/cairo-lint", rev = "b95a1949b932e89179c052efdbf4e21002ce6777" } [profile.release] lto = true diff --git a/scarb/Cargo.toml b/scarb/Cargo.toml index 6bfbb5822..580fe5a74 100644 --- a/scarb/Cargo.toml +++ b/scarb/Cargo.toml @@ -34,6 +34,7 @@ cairo-lang-starknet.workspace = true cairo-lang-syntax.workspace = true cairo-lang-test-plugin.workspace = true cairo-lang-utils.workspace = true +cairo-lint-core.workspace = true camino.workspace = true clap.workspace = true convert_case.workspace = true @@ -116,6 +117,7 @@ similar-asserts.workspace = true snapbox.workspace = true test-case.workspace = true test-for-each-example = { path = "../utils/test-for-each-example" } +cairo-lint-core = { workspace = true, features = ["testing-colors"] } [build-dependencies] fs_extra.workspace = true diff --git a/scarb/src/bin/scarb/args.rs b/scarb/src/bin/scarb/args.rs index 6b4dee981..95f94afb1 100644 --- a/scarb/src/bin/scarb/args.rs +++ b/scarb/src/bin/scarb/args.rs @@ -187,6 +187,8 @@ pub enum Command { to a registry. ")] Publish(PublishArgs), + /// Checks a package to catch common mistakes and improve your Cairo code. + Lint(LintArgs), /// Run arbitrary package scripts. Run(ScriptsRunnerArgs), /// Execute all unit and integration tests of a local package. @@ -531,6 +533,25 @@ pub struct PublishArgs { pub ignore_cairo_version: bool, } +#[derive(Parser, Clone, Debug)] +pub struct LintArgs { + /// Name of the package. + #[command(flatten)] + pub packages_filter: PackagesFilter, + + /// Should lint the tests. + #[arg(short, long, default_value_t = false)] + pub test: bool, + + /// Should fix the lint when it can. + #[arg(short, long, default_value_t = false)] + pub fix: bool, + + /// Do not error on `cairo-version` mismatch. + #[arg(long)] + pub ignore_cairo_version: bool, +} + /// Git reference specification arguments. #[derive(Parser, Clone, Debug)] #[group(requires = "git", multiple = false)] diff --git a/scarb/src/bin/scarb/commands/lint.rs b/scarb/src/bin/scarb/commands/lint.rs new file mode 100644 index 000000000..d6bcbac6c --- /dev/null +++ b/scarb/src/bin/scarb/commands/lint.rs @@ -0,0 +1,25 @@ +use scarb::ops::{self, LintOptions}; + +use crate::args::LintArgs; +use anyhow::Result; +use scarb::core::Config; + +#[tracing::instrument(skip_all, level = "info")] +pub fn run(args: LintArgs, config: &Config) -> Result<()> { + let ws = ops::read_workspace(config.manifest_path(), config)?; + let packages = args + .packages_filter + .match_many(&ws)? + .into_iter() + .collect::>(); + + ops::lint( + LintOptions { + packages, + test: args.test, + fix: args.fix, + ignore_cairo_version: args.ignore_cairo_version, + }, + &ws, + ) +} diff --git a/scarb/src/bin/scarb/commands/mod.rs b/scarb/src/bin/scarb/commands/mod.rs index 8dcde4ba7..2565d9ae5 100644 --- a/scarb/src/bin/scarb/commands/mod.rs +++ b/scarb/src/bin/scarb/commands/mod.rs @@ -18,6 +18,7 @@ pub mod external; pub mod fetch; pub mod fmt; pub mod init; +mod lint; pub mod manifest_path; pub mod metadata; pub mod new; @@ -52,6 +53,7 @@ pub fn run(command: Command, config: &mut Config) -> Result<()> { Package(args) => package::run(args, config), ProcMacroServer => proc_macro_server::run(config), Publish(args) => publish::run(args, config), + Lint(args) => lint::run(args, config), Remove(args) => remove::run(args, config), Run(args) => run::run(args, config), Test(args) => test::run(args, config), diff --git a/scarb/src/compiler/db.rs b/scarb/src/compiler/db.rs index 66fad856d..702ef43c0 100644 --- a/scarb/src/compiler/db.rs +++ b/scarb/src/compiler/db.rs @@ -12,6 +12,7 @@ use cairo_lang_filesystem::db::{ AsFilesGroupMut, CrateIdentifier, CrateSettings, DependencySettings, FilesGroup, FilesGroupEx, }; use cairo_lang_filesystem::ids::CrateLongId; +use cairo_lang_semantic::plugin::PluginSuite; use cairo_lang_utils::ordered_hash_map::OrderedHashMap; use smol_str::SmolStr; use std::path::PathBuf; @@ -26,12 +27,13 @@ pub struct ScarbDatabase { pub(crate) fn build_scarb_root_database( unit: &CairoCompilationUnit, ws: &Workspace<'_>, + additional_plugins: Vec, ) -> Result { let mut b = RootDatabase::builder(); b.with_project_config(build_project_config(unit)?); b.with_cfg(unit.cfg_set.clone()); b.with_inlining_strategy(unit.compiler_config.inlining_strategy.clone().into()); - let proc_macro_host = load_plugins(unit, ws, &mut b)?; + let proc_macro_host = load_plugins(unit, ws, &mut b, additional_plugins)?; if !unit.compiler_config.enable_gas { b.skip_auto_withdraw_gas(); } @@ -50,6 +52,7 @@ fn load_plugins( unit: &CairoCompilationUnit, ws: &Workspace<'_>, builder: &mut RootDatabaseBuilder, + additional_plugins: Vec, ) -> Result> { let mut proc_macros = ProcMacroHost::default(); for plugin_info in &unit.cairo_plugins { @@ -64,6 +67,9 @@ fn load_plugins( proc_macros.register_new(plugin_info.package.clone(), ws.config())?; } } + for plugin in additional_plugins { + builder.with_plugin_suite(plugin); + } let macro_host = Arc::new(proc_macros.into_plugin()?); builder.with_plugin_suite(ProcMacroHostPlugin::build_plugin_suite(macro_host.clone())); Ok(macro_host) diff --git a/scarb/src/ops/compile.rs b/scarb/src/ops/compile.rs index 565cfbb09..f63a82471 100644 --- a/scarb/src/ops/compile.rs +++ b/scarb/src/ops/compile.rs @@ -219,7 +219,7 @@ fn compile_unit_inner(unit: CompilationUnit, ws: &Workspace<'_>) -> Result<()> { let ScarbDatabase { mut db, proc_macro_host, - } = build_scarb_root_database(&unit, ws)?; + } = build_scarb_root_database(&unit, ws, Default::default())?; check_starknet_dependency(&unit, ws, &db, &package_name); let result = ws.config().compilers().compile(unit, &mut db, ws); proc_macro_host @@ -284,7 +284,8 @@ fn check_unit(unit: CompilationUnit, ws: &Workspace<'_>) -> Result<()> { let result = match unit { CompilationUnit::ProcMacro(unit) => proc_macro::check_unit(unit, ws), CompilationUnit::Cairo(unit) => { - let ScarbDatabase { db, .. } = build_scarb_root_database(&unit, ws)?; + let ScarbDatabase { db, .. } = + build_scarb_root_database(&unit, ws, Default::default())?; let main_crate_ids = collect_main_crate_ids(&unit, &db); check_starknet_dependency(&unit, ws, &db, &package_name); let mut compiler_config = build_compiler_config(&db, &unit, &main_crate_ids, ws); diff --git a/scarb/src/ops/expand.rs b/scarb/src/ops/expand.rs index e5a47f218..08fcf47ee 100644 --- a/scarb/src/ops/expand.rs +++ b/scarb/src/ops/expand.rs @@ -168,7 +168,8 @@ fn do_expand( opts: ExpandOpts, ws: &Workspace<'_>, ) -> Result<()> { - let ScarbDatabase { db, .. } = build_scarb_root_database(compilation_unit, ws)?; + let ScarbDatabase { db, .. } = + build_scarb_root_database(compilation_unit, ws, Default::default())?; let name = compilation_unit.main_component().cairo_package_name(); let main_crate_id = db.intern_crate(CrateLongId::Real { name, diff --git a/scarb/src/ops/lint.rs b/scarb/src/ops/lint.rs new file mode 100644 index 000000000..50d037416 --- /dev/null +++ b/scarb/src/ops/lint.rs @@ -0,0 +1,248 @@ +use std::{collections::HashSet, vec}; + +use crate::{ + compiler::{ + db::{build_scarb_root_database, ScarbDatabase}, + CompilationUnit, CompilationUnitAttributes, + }, + core::{PackageId, TargetKind}, + ops, +}; +use anyhow::anyhow; +use anyhow::Result; +use cairo_lang_defs::db::DefsGroup; +use cairo_lang_diagnostics::Diagnostics; +use cairo_lang_filesystem::db::FilesGroup; +use cairo_lang_filesystem::ids::CrateLongId; +use cairo_lang_semantic::diagnostic::SemanticDiagnosticKind; +use cairo_lang_semantic::{db::SemanticGroup, SemanticDiagnostic}; +use cairo_lang_utils::Upcast; +use cairo_lint_core::annotate_snippets::Renderer; +use cairo_lint_core::{ + apply_file_fixes, + diagnostics::format_diagnostic, + get_fixes, + plugin::{cairo_lint_plugin_suite, diagnostic_kind_from_message, CairoLintKind}, +}; +use itertools::Itertools; +use scarb_ui::components::Status; +use serde::Deserialize; +use smol_str::SmolStr; + +use crate::core::{Package, Workspace}; + +use super::{compile_unit, CompilationUnitsOpts, FeaturesOpts, FeaturesSelector}; + +pub struct LintOptions { + pub packages: Vec, + pub test: bool, + pub fix: bool, + pub ignore_cairo_version: bool, +} + +#[tracing::instrument(skip_all, level = "debug")] +pub fn lint(opts: LintOptions, ws: &Workspace<'_>) -> Result<()> { + let feature_opts = FeaturesOpts { + features: FeaturesSelector::AllFeatures, + no_default_features: true, + }; + + let resolve = ops::resolve_workspace(ws)?; + + let compilation_units = ops::generate_compilation_units( + &resolve, + &feature_opts, + ws, + CompilationUnitsOpts { + ignore_cairo_version: opts.ignore_cairo_version, + load_prebuilt_macros: true, + }, + )?; + + // Select proc macro units that need to be compiled for Cairo compilation units. + let required_plugins = compilation_units + .iter() + .flat_map(|unit| match unit { + CompilationUnit::Cairo(unit) => unit + .cairo_plugins + .iter() + .map(|p| p.package.id) + .collect_vec(), + _ => Vec::new(), + }) + .collect::>(); + + // We process all proc-macro units that are required by Cairo compilation units beforehand. + for compilation_unit in compilation_units.iter() { + if let CompilationUnit::ProcMacro(_) = compilation_unit { + if required_plugins.contains(&compilation_unit.main_package_id()) { + compile_unit(compilation_unit.clone(), ws)?; + } + } + } + + for package in opts.packages { + let package_compilation_units = if opts.test { + let mut result = vec![]; + let integration_test_compilation_unit = + find_integration_test_package_id(&package).map(|id| { + compilation_units + .iter() + .find(|compilation_unit| compilation_unit.main_package_id() == id) + .unwrap() + }); + + // We also want to get the main compilation unit for the package. + if let Some(cu) = compilation_units.iter().find(|compilation_unit| { + compilation_unit.main_package_id() == package.id + && compilation_unit.main_component().target_kind() != TargetKind::TEST + }) { + result.push(cu) + } + + // We get all the compilation units with target kind set to "test". + result.extend(compilation_units.iter().filter(|compilation_unit| { + compilation_unit.main_package_id() == package.id + && compilation_unit.main_component().target_kind() == TargetKind::TEST + })); + + // If any integration test compilation unit was found, we add it to the result. + if let Some(integration_test_compilation_unit) = integration_test_compilation_unit { + result.push(integration_test_compilation_unit); + } + + if result.is_empty() { + return Err(anyhow!( + "No Cairo compilation unit found for package {}.", + package.id + )); + } + + result + } else { + let found_compilation_unit = + compilation_units + .iter() + .find(|compilation_unit| match compilation_unit { + CompilationUnit::Cairo(compilation_unit) => { + compilation_unit.main_package_id() == package.id + && compilation_unit.main_component().target_kind() + != TargetKind::TEST + } + _ => false, + }); + vec![found_compilation_unit.ok_or(anyhow!( + "No Cairo compilation unit found for package {}. Try running `--test` to include tests.", + package.id + ))?] + }; + + for compilation_unit in package_compilation_units { + match compilation_unit { + CompilationUnit::ProcMacro(_) => { + continue; + } + CompilationUnit::Cairo(compilation_unit) => { + ws.config() + .ui() + .print(Status::new("Linting", &compilation_unit.name())); + + let additional_plugins = vec![cairo_lint_plugin_suite()]; + let ScarbDatabase { db, .. } = + build_scarb_root_database(compilation_unit, ws, additional_plugins)?; + + let main_component = compilation_unit.main_component(); + + let crate_id = db.intern_crate(CrateLongId::Real { + name: SmolStr::new(main_component.target_name()), + discriminator: main_component.id.to_discriminator(), + }); + + let diags: Vec> = db + .crate_modules(crate_id) + .iter() + .flat_map(|module_id| db.module_semantic_diagnostics(*module_id).ok()) + .collect(); + + let should_lint_panics = cairo_lint_tool_metadata(&package)?.nopanic; + + let renderer = Renderer::styled(); + + let diagnostics = diags + .iter() + .flat_map(|diags| { + let all_diags = diags.get_all(); + all_diags + .iter() + .filter(|diag| { + if let SemanticDiagnosticKind::PluginDiagnostic(diag) = + &diag.kind + { + (matches!( + diagnostic_kind_from_message(&diag.message), + CairoLintKind::Panic + ) && should_lint_panics) + || !matches!( + diagnostic_kind_from_message(&diag.message), + CairoLintKind::Panic + ) + } else { + true + } + }) + .for_each(|diag| { + ws.config() + .ui() + .print(format_diagnostic(diag, &db, &renderer)) + }); + all_diags + }) + .collect::>(); + + if opts.fix { + let fixes = get_fixes(&db, diagnostics)?; + for (file_id, fixes) in fixes.into_iter() { + ws.config() + .ui() + .print(Status::new("Fixing", &file_id.file_name(db.upcast()))); + apply_file_fixes(file_id, fixes, &db)?; + } + } + } + } + } + } + Ok(()) +} + +#[derive(Deserialize, Default, Debug)] +pub struct CairoLintToolMetadata { + pub nopanic: bool, +} + +fn cairo_lint_tool_metadata(package: &Package) -> Result { + Ok(package + .tool_metadata("cairo-lint") + .cloned() + .map(toml::Value::try_into) + .transpose()? + .unwrap_or_default()) +} + +fn find_integration_test_package_id(package: &Package) -> Option { + let integration_target = package.manifest.targets.iter().find(|target| { + target.kind == TargetKind::TEST + && target + .params + .get("test-type") + .and_then(|v| v.as_str()) + .unwrap_or_default() + == "integration" + }); + + integration_target.map(|target| { + package + .id + .for_test_target(target.group_id.clone().unwrap_or(target.name.clone())) + }) +} diff --git a/scarb/src/ops/mod.rs b/scarb/src/ops/mod.rs index df3d5ac7c..946937d5d 100644 --- a/scarb/src/ops/mod.rs +++ b/scarb/src/ops/mod.rs @@ -7,6 +7,7 @@ pub use clean::*; pub use compile::*; pub use expand::*; pub use fmt::*; +pub use lint::*; pub use manifest::*; pub use metadata::*; pub use new::*; @@ -23,6 +24,7 @@ mod clean; mod compile; mod expand; mod fmt; +mod lint; mod lockfile; mod manifest; mod metadata; diff --git a/scarb/tests/lint.rs b/scarb/tests/lint.rs new file mode 100644 index 000000000..dee5fde57 --- /dev/null +++ b/scarb/tests/lint.rs @@ -0,0 +1,241 @@ +use assert_fs::fixture::FileWriteStr; +use assert_fs::{prelude::PathChild, TempDir}; +use indoc::{formatdoc, indoc}; +use scarb_test_support::{ + command::Scarb, project_builder::ProjectBuilder, workspace_builder::WorkspaceBuilder, +}; + +#[test] +fn lint_main_package() { + let test_code = indoc! {r#" + use hello::f1; + #[test] + fn it_works() { + let x = true; + if false == x { + println!("x is false"); + } + assert_eq!(1, f1()); + } + "#}; + let t = TempDir::new().unwrap(); + ProjectBuilder::start() + .name("hello") + .lib_cairo(formatdoc! {r#" + fn main() {{ + let x = true; + if x == false {{ + println!("x is false"); + }} + }} + + // This should not be checked. + #[cfg(test)] + mod tests {{ + {test_code} + }} + "#}) + .build(&t); + + // We add this one to test that the linting is not run on the test package. + t.child("tests/test1.cairo").write_str(test_code).unwrap(); + + Scarb::quick_snapbox() + .arg("lint") + .current_dir(&t) + .assert() + .success() + // Current expected values include ANSI color codes because lint has custom renderer. + .stdout_matches(indoc! {r#" + Linting hello v1.0.0 ([..]/Scarb.toml) + warning: Plugin diagnostic: Unnecessary comparison with a boolean value. Use the variable directly. + --> [..]/lib.cairo:3:8 + | + 3 | if x == false { + | ---------- + | + + "#}); +} + +#[test] +fn lint_workspace() { + let t = TempDir::new().unwrap(); + ProjectBuilder::start() + .name("first") + .lib_cairo(indoc! {r#" + fn main() { + let first = true; + if first == false { + println!("x is false"); + } + } + "#}) + .build(&t.child("first")); + ProjectBuilder::start() + .name("second") + .lib_cairo(indoc! {r#" + fn main() { + let second = true; + if second == false { + println!("x is false"); + } + } + "#}) + .build(&t.child("second")); + + WorkspaceBuilder::start() + .add_member("first") + .add_member("second") + .package(ProjectBuilder::start().name("main").lib_cairo(indoc! {r#" + fn main() { + let _main = true; + if _main == false { + println!("x is false"); + } + } + "#})) + .build(&t); + + Scarb::quick_snapbox() + .arg("lint") + .arg("--workspace") + .current_dir(&t) + .assert() + .success() + // Current expected values include ANSI color codes because lint has custom renderer. + .stdout_matches(indoc! {r#" + Linting first v1.0.0 ([..]/first/Scarb.toml) + warning: Plugin diagnostic: Unnecessary comparison with a boolean value. Use the variable directly. + --> [..]/lib.cairo:3:8 + | + 3 | if first == false { + | -------------- + | + + Linting main v1.0.0 ([..]/Scarb.toml) + warning: Plugin diagnostic: Unnecessary comparison with a boolean value. Use the variable directly. + --> [..]/lib.cairo:3:8 + | + 3 | if _main == false { + | -------------- + | + + Linting second v1.0.0 ([..]/second/Scarb.toml) + warning: Plugin diagnostic: Unnecessary comparison with a boolean value. Use the variable directly. + --> [..]/lib.cairo:3:8 + | + 3 | if second == false { + | --------------- + | + + "#}); +} + +#[test] +fn lint_integration_tests() { + let t = TempDir::new().unwrap(); + ProjectBuilder::start() + .name("hello") + .lib_cairo(indoc! {r#" + pub fn f1() -> u32 { + 42 + } + + fn main() { + // This is a comment + } + "#}) + .dep_cairo_test() + .build(&t); + t.child("tests/test1.cairo") + .write_str(indoc! {r#" + use hello::f1; + #[test] + fn it_works() { + let x = true; + if false == x { + println!("x is false"); + } + assert_eq!(1, f1()); + } + "#}) + .unwrap(); + + Scarb::quick_snapbox() + .arg("lint") + .arg("-t") + .current_dir(&t) + .assert() + .success() + // Current expected values include ANSI color codes because lint has custom renderer. + .stdout_matches(indoc! {r#" + Linting hello v1.0.0 ([..]/Scarb.toml) + Linting test(hello_unittest) hello v1.0.0 ([..]/Scarb.toml) + Linting test(hello_integrationtest) hello_integrationtest v1.0.0 ([..]/Scarb.toml) + warning: Plugin diagnostic: Unnecessary comparison with a boolean value. Use the variable directly. + --> [..]/tests/test1.cairo:5:8 + | + 5 | if false == x { + | ---------- + | + + "#}); +} + +#[test] +fn lint_unit_test() { + let t = TempDir::new().unwrap(); + ProjectBuilder::start() + .name("hello") + .dep_cairo_test() + .manifest_extra( + r#" + [[test]] + test-type = "unit" + "#, + ) + .lib_cairo(indoc! {r#" + pub fn f1() -> u32 { + 42 + } + + fn main() { + // This is a comment + } + + #[cfg(test)] + mod tests { + use hello::f1; + #[test] + fn it_works() { + let x = true; + if false == x { + println!("x is false"); + } + assert_eq!(1, f1()); + } + } + "#}) + .dep_cairo_test() + .build(&t); + + Scarb::quick_snapbox() + .arg("lint") + .arg("-t") + .current_dir(&t) + .assert() + .success() + // Current expected values include ANSI color codes because lint has custom renderer. + .stdout_matches(indoc! {r#" + Linting hello v1.0.0 ([..]/Scarb.toml) + Linting test(hello) hello v1.0.0 ([..]/Scarb.toml) + warning: Plugin diagnostic: Unnecessary comparison with a boolean value. Use the variable directly. + --> [..]/lib.cairo:15:12 + | + 15 | if false == x { + | ---------- + | + + "#}); +}