Skip to content

Commit

Permalink
feat(zk_toolbox): Add lint command (#2626)
Browse files Browse the repository at this point in the history
## What ❔
Add lint command
  • Loading branch information
matias-gonz authored Aug 13, 2024
1 parent ae2dd3b commit 3d02946
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 2 deletions.
189 changes: 189 additions & 0 deletions zk_toolbox/crates/zk_supervisor/src/commands/lint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
use clap::{Parser, ValueEnum};
use common::{cmd::Cmd, logger, spinner::Spinner};
use config::EcosystemConfig;
use strum::EnumIter;
use xshell::{cmd, Shell};

use crate::messages::{
msg_running_linter_for_extension_spinner, msg_running_linters_for_files,
MSG_LINT_CONFIG_PATH_ERR, MSG_RUNNING_CONTRACTS_LINTER_SPINNER,
};

const IGNORED_DIRS: [&str; 18] = [
"target",
"node_modules",
"volumes",
"build",
"dist",
".git",
"generated",
"grafonnet-lib",
"prettier-config",
"lint-config",
"cache",
"artifacts",
"typechain",
"binaryen",
"system-contracts",
"artifacts-zk",
"cache-zk",
// Ignore directories with OZ and forge submodules.
"contracts/l1-contracts/lib",
];

const IGNORED_FILES: [&str; 4] = [
"KeysWithPlonkVerifier.sol",
"TokenInit.sol",
".tslintrc.js",
".prettierrc.js",
];

const CONFIG_PATH: &str = "etc/lint-config";

#[derive(Debug, Parser)]
pub struct LintArgs {
#[clap(long, short = 'c')]
pub check: bool,
#[clap(long, short = 'e')]
pub extensions: Vec<Extension>,
}

#[derive(Debug, ValueEnum, EnumIter, strum::Display, PartialEq, Eq, Clone)]
#[strum(serialize_all = "lowercase")]
pub enum Extension {
Rs,
Md,
Sol,
Js,
Ts,
}

pub fn run(shell: &Shell, args: LintArgs) -> anyhow::Result<()> {
let extensions = if args.extensions.is_empty() {
vec![
Extension::Rs,
Extension::Md,
Extension::Sol,
Extension::Js,
Extension::Ts,
]
} else {
args.extensions.clone()
};

logger::info(msg_running_linters_for_files(&extensions));

let ecosystem = EcosystemConfig::from_file(shell)?;

for extension in extensions {
match extension {
Extension::Rs => lint_rs(shell, &ecosystem)?,
Extension::Sol => lint_contracts(shell, &ecosystem, args.check)?,
ext => lint(shell, &ecosystem, &ext, args.check)?,
}
}

Ok(())
}

fn lint_rs(shell: &Shell, ecosystem: &EcosystemConfig) -> anyhow::Result<()> {
let spinner = Spinner::new(&msg_running_linter_for_extension_spinner(&Extension::Rs));

let link_to_code = &ecosystem.link_to_code;
let lint_to_prover = &ecosystem.link_to_code.join("prover");
let link_to_toolbox = &ecosystem.link_to_code.join("zk_toolbox");
let paths = vec![link_to_code, lint_to_prover, link_to_toolbox];

for path in paths {
let _dir_guard = shell.push_dir(path);
Cmd::new(cmd!(
shell,
"cargo clippy --locked -- -D warnings -D unstable_features"
))
.run()?;
}

spinner.finish();

Ok(())
}

fn get_linter(extension: &Extension) -> Vec<String> {
match extension {
Extension::Rs => vec!["cargo".to_string(), "clippy".to_string()],
Extension::Md => vec!["markdownlint".to_string()],
Extension::Sol => vec!["solhint".to_string()],
Extension::Js => vec!["eslint".to_string()],
Extension::Ts => vec!["eslint".to_string(), "--ext".to_string(), "ts".to_string()],
}
}

fn lint(
shell: &Shell,
ecosystem: &EcosystemConfig,
extension: &Extension,
check: bool,
) -> anyhow::Result<()> {
let spinner = Spinner::new(&msg_running_linter_for_extension_spinner(extension));
let _dir_guard = shell.push_dir(&ecosystem.link_to_code);
let files = get_unignored_files(shell, extension)?;

let cmd = cmd!(shell, "yarn");
let config_path = ecosystem.link_to_code.join(CONFIG_PATH);
let config_path = config_path.join(format!("{}.js", extension));
let config_path = config_path
.to_str()
.expect(MSG_LINT_CONFIG_PATH_ERR)
.to_string();

let linter = get_linter(extension);

let fix_option = if check {
vec![]
} else {
vec!["--fix".to_string()]
};

let args = [
linter.as_slice(),
&fix_option,
&["--config".to_string(), config_path],
files.as_slice(),
]
.concat();

Cmd::new(cmd.args(&args)).run()?;
spinner.finish();
Ok(())
}

fn lint_contracts(shell: &Shell, ecosystem: &EcosystemConfig, check: bool) -> anyhow::Result<()> {
lint(shell, ecosystem, &Extension::Sol, check)?;

let spinner = Spinner::new(MSG_RUNNING_CONTRACTS_LINTER_SPINNER);
let _dir_guard = shell.push_dir(&ecosystem.link_to_code);
let cmd = cmd!(shell, "yarn");
let linter = if check { "lint:check" } else { "lint:fix" };
let args = ["--cwd", "contracts", linter];
Cmd::new(cmd.args(&args)).run()?;
spinner.finish();

Ok(())
}

fn get_unignored_files(shell: &Shell, extension: &Extension) -> anyhow::Result<Vec<String>> {
let mut files = Vec::new();
let output = cmd!(shell, "git ls-files").read()?;

for line in output.lines() {
let path = line.to_string();
if !IGNORED_DIRS.iter().any(|dir| path.contains(dir))
&& !IGNORED_FILES.contains(&path.as_str())
&& path.ends_with(&format!(".{}", extension))
{
files.push(path);
}
}

Ok(files)
}
1 change: 1 addition & 0 deletions zk_toolbox/crates/zk_supervisor/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod clean;
pub mod database;
pub mod lint;
pub mod snapshot;
pub mod test;
9 changes: 7 additions & 2 deletions zk_toolbox/crates/zk_supervisor/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use clap::{Parser, Subcommand};
use commands::{database::DatabaseCommands, snapshot::SnapshotCommands, test::TestCommands};
use commands::{
database::DatabaseCommands, lint::LintArgs, snapshot::SnapshotCommands, test::TestCommands,
};
use common::{
check_general_prerequisites,
config::{global_config, init_global_config, GlobalConfig},
Expand All @@ -9,7 +11,7 @@ use common::{
use config::EcosystemConfig;
use messages::{
msg_global_chain_does_not_exist, MSG_SUBCOMMAND_CLEAN, MSG_SUBCOMMAND_DATABASE_ABOUT,
MSG_SUBCOMMAND_TESTS_ABOUT,
MSG_SUBCOMMAND_LINT_ABOUT, MSG_SUBCOMMAND_TESTS_ABOUT,
};
use xshell::Shell;

Expand Down Expand Up @@ -38,6 +40,8 @@ enum SupervisorSubcommands {
Clean(CleanCommands),
#[command(subcommand, about = "Snapshots creator")]
Snapshot(SnapshotCommands),
#[command(about = MSG_SUBCOMMAND_LINT_ABOUT, alias = "l")]
Lint(LintArgs),
#[command(hide = true)]
Markdown,
}
Expand Down Expand Up @@ -94,6 +98,7 @@ async fn run_subcommand(args: Supervisor, shell: &Shell) -> anyhow::Result<()> {
SupervisorSubcommands::Markdown => {
clap_markdown::print_help_markdown::<Supervisor>();
}
SupervisorSubcommands::Lint(args) => commands::lint::run(shell, args)?,
}
Ok(())
}
Expand Down
19 changes: 19 additions & 0 deletions zk_toolbox/crates/zk_supervisor/src/messages.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::commands::lint::Extension;

// Ecosystem related messages
pub(super) const MSG_CHAIN_NOT_FOUND_ERR: &str = "Chain not found";

Expand All @@ -9,6 +11,7 @@ pub(super) fn msg_global_chain_does_not_exist(chain: &str, available_chains: &st
pub(super) const MSG_SUBCOMMAND_DATABASE_ABOUT: &str = "Database related commands";
pub(super) const MSG_SUBCOMMAND_TESTS_ABOUT: &str = "Run tests";
pub(super) const MSG_SUBCOMMAND_CLEAN: &str = "Clean artifacts";
pub(super) const MSG_SUBCOMMAND_LINT_ABOUT: &str = "Lint code";

// Database related messages
pub(super) const MSG_NO_DATABASES_SELECTED: &str = "No databases selected";
Expand Down Expand Up @@ -135,3 +138,19 @@ pub(super) const MSG_CONTRACTS_CLEANING_FINISHED: &str =

/// Snapshot creator related messages
pub(super) const MSG_RUNNING_SNAPSHOT_CREATOR: &str = "Running snapshot creator";

// Lint related messages
pub(super) fn msg_running_linters_for_files(extensions: &[Extension]) -> String {
let extensions: Vec<String> = extensions.iter().map(|e| format!(".{}", e)).collect();
format!(
"Running linters for files with extensions: {:?}",
extensions
)
}

pub(super) fn msg_running_linter_for_extension_spinner(extension: &Extension) -> String {
format!("Running linter for files with extension: .{}", extension)
}

pub(super) const MSG_LINT_CONFIG_PATH_ERR: &str = "Lint config path error";
pub(super) const MSG_RUNNING_CONTRACTS_LINTER_SPINNER: &str = "Running contracts linter..";

0 comments on commit 3d02946

Please sign in to comment.