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(zk_toolbox): Add lint command #2626

Merged
merged 8 commits into from
Aug 13, 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
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()],
Deniallugo marked this conversation as resolved.
Show resolved Hide resolved
}
}

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..";
Loading