diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 81366184..e7e85289 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -133,6 +133,10 @@ jobs: name: linux-x64 path: release + - name: Zip Linux x64 + run: | + cd release && zip linux-x64.zip hemtt && rm hemtt + - name: Rename Linux x64 run: | cd release && mv hemtt linux-x64 @@ -153,6 +157,10 @@ jobs: name: macos-x64 path: release + - name: Zip MacOS x64 + run: | + cd release && zip macos-x64.zip hemtt && rm hemtt + - name: Rename MacOS x64 run: | cd release && mv hemtt darwin-x64 diff --git a/Cargo.lock b/Cargo.lock index 8691c866..1bda85b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1416,6 +1416,7 @@ dependencies = [ "hemtt-sqf", "hemtt-stringtable", "hemtt-workspace", + "indicatif", "num_cpus", "paste", "rayon", @@ -1610,11 +1611,15 @@ dependencies = [ name = "hemtt-stringtable" version = "1.0.0" dependencies = [ - "indexmap", + "automod", + "hemtt-common", + "hemtt-workspace", "insta", + "linkme", "paste", "quick-xml", "serde", + "tracing", ] [[package]] @@ -1626,6 +1631,8 @@ dependencies = [ "dirs", "hemtt-common", "hemtt-pbo", + "linkme", + "paste", "serde", "supports-hyperlinks", "terminal-link", @@ -1947,6 +1954,19 @@ dependencies = [ "hashbrown 0.15.0", ] +[[package]] +name = "indicatif" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + [[package]] name = "inotify" version = "0.9.6" @@ -2681,6 +2701,12 @@ dependencies = [ "libc", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "objc-sys" version = "0.3.5" @@ -3072,6 +3098,12 @@ dependencies = [ "miniz_oxide 0.8.0", ] +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "powerfmt" version = "0.2.0" diff --git a/bin/Cargo.toml b/bin/Cargo.toml index 8be91117..d6c44a74 100644 --- a/bin/Cargo.toml +++ b/bin/Cargo.toml @@ -38,6 +38,7 @@ dirs = { workspace = true } fs_extra = "1.3.0" git2 = { workspace = true } glob = "0.3.1" +indicatif = "0.17.8" num_cpus = "1.16.0" paste = { workspace = true } rayon = "1.10.0" diff --git a/bin/src/commands/build.rs b/bin/src/commands/build.rs index 5a08eccd..61d3eb9c 100644 --- a/bin/src/commands/build.rs +++ b/bin/src/commands/build.rs @@ -4,10 +4,12 @@ use crate::{ context::{self, Context}, error::Error, executor::Executor, - modules::{bom::BOMCheck, pbo::Collapse, Binarize, Files, Hooks, Rapifier, SQFCompiler}, + modules::{pbo::Collapse, Binarize, Files, Rapifier}, report::Report, }; +use super::global_modules; + #[must_use] pub fn cli() -> Command { add_just(add_args( @@ -82,15 +84,13 @@ pub fn execute(matches: &ArgMatches) -> Result { #[must_use] pub fn executor(ctx: Context, matches: &ArgMatches) -> Executor { let mut executor = Executor::new(ctx); + global_modules(&mut executor); executor.collapse(Collapse::No); - executor.add_module(Box::::default()); - executor.add_module(Box::::default()); if matches.get_one::("no-rap") != Some(&true) { executor.add_module(Box::::default()); } - executor.add_module(Box::new(SQFCompiler::default())); if matches.get_one::("no-bin") != Some(&true) { executor.add_module(Box::::default()); } diff --git a/bin/src/commands/check.rs b/bin/src/commands/check.rs index 8d7913e9..d091fa20 100644 --- a/bin/src/commands/check.rs +++ b/bin/src/commands/check.rs @@ -1,10 +1,11 @@ use clap::Command; use crate::{ + commands::global_modules, context::Context, error::Error, executor::Executor, - modules::{bom::BOMCheck, pbo::Collapse, Binarize, Hooks, Rapifier, SQFCompiler}, + modules::{pbo::Collapse, Binarize, Rapifier}, report::Report, }; @@ -25,13 +26,11 @@ pub fn execute() -> Result { )?; let mut executor = Executor::new(ctx); + global_modules(&mut executor); executor.collapse(Collapse::Yes); - executor.add_module(Box::::default()); - executor.add_module(Box::::default()); executor.add_module(Box::::default()); - executor.add_module(Box::::default()); executor.add_module(Box::::new(Binarize::new(true))); info!("Running checks"); diff --git a/bin/src/commands/dev.rs b/bin/src/commands/dev.rs index 35fcc5f0..7a9837cb 100644 --- a/bin/src/commands/dev.rs +++ b/bin/src/commands/dev.rs @@ -2,12 +2,11 @@ use clap::{ArgAction, ArgMatches, Command}; use hemtt_workspace::addons::Location; use crate::{ + commands::global_modules, context::Context, error::Error, executor::Executor, - modules::{ - bom::BOMCheck, pbo::Collapse, Binarize, FilePatching, Files, Hooks, Rapifier, SQFCompiler, - }, + modules::{pbo::Collapse, Binarize, FilePatching, Files, Rapifier}, report::Report, }; @@ -124,15 +123,13 @@ pub fn context( } let mut executor = Executor::new(ctx); + global_modules(&mut executor); executor.collapse(Collapse::Yes); - executor.add_module(Box::::default()); - executor.add_module(Box::::default()); if rapify && matches.get_one::("no-rap") != Some(&true) { executor.add_module(Box::::default()); } - executor.add_module(Box::new(SQFCompiler::default())); executor.add_module(Box::::default()); executor.add_module(Box::::default()); if force_binarize || matches.get_one::("binarize") == Some(&true) { diff --git a/bin/src/commands/mod.rs b/bin/src/commands/mod.rs index 8e6581af..d3734913 100644 --- a/bin/src/commands/mod.rs +++ b/bin/src/commands/mod.rs @@ -10,3 +10,15 @@ pub mod script; pub mod utils; pub mod value; pub mod wiki; + +/// Adds modules that should apply to: +/// - hemtt check +/// - hemtt dev +/// - hemtt build +/// - hemtt release +pub fn global_modules(executor: &mut crate::executor::Executor) { + executor.add_module(Box::::default()); + executor.add_module(Box::::default()); + executor.add_module(Box::::default()); + executor.add_module(Box::::default()); +} diff --git a/bin/src/lib.rs b/bin/src/lib.rs index c4c88a23..2065771a 100644 --- a/bin/src/lib.rs +++ b/bin/src/lib.rs @@ -11,6 +11,7 @@ pub mod executor; pub mod link; pub mod logging; pub mod modules; +mod progress; pub mod report; pub mod update; pub mod utils; diff --git a/bin/src/modules/archive.rs b/bin/src/modules/archive.rs index e6399730..479bc828 100644 --- a/bin/src/modules/archive.rs +++ b/bin/src/modules/archive.rs @@ -1,9 +1,17 @@ -use std::fs::{create_dir_all, File}; +use std::{ + fs::{create_dir_all, File}, + path::PathBuf, +}; use walkdir::WalkDir; use zip::{write::SimpleFileOptions, ZipWriter}; -use crate::{context::Context, error::Error, report::Report}; +use crate::{context::Context, error::Error, progress::progress_bar, report::Report}; + +enum Entry { + File(String, PathBuf), + Directory(String), +} /// Creates the release zips /// @@ -27,7 +35,7 @@ pub fn release(ctx: &Context) -> Result { let options = SimpleFileOptions::default().compression_level(Some(9)); debug!("creating release at {:?}", output.display()); - let mut zip = ZipWriter::new(File::create(&output)?); + let mut to_write = Vec::new(); for entry in WalkDir::new(ctx.build_folder().expect("build folder exists")) { let Ok(entry) = entry else { continue; @@ -48,7 +56,7 @@ pub fn release(ctx: &Context) -> Result { path.replace('\\', "/") ); trace!("zip: creating directory {:?}", dir); - zip.add_directory(dir, options)?; + to_write.push(Entry::Directory(dir)); continue; } let name = path @@ -60,9 +68,23 @@ pub fn release(ctx: &Context) -> Result { name.display().to_string().replace('\\', "/") ); trace!("zip: adding file {:?}", file); - zip.start_file(file, options)?; - std::io::copy(&mut File::open(path)?, &mut zip)?; + to_write.push(Entry::File(file, path.to_owned())); + } + let progress = progress_bar(to_write.len() as u64).with_message("Creating release"); + let mut zip = ZipWriter::new(File::create(&output)?); + for entry in to_write { + match entry { + Entry::File(file, path) => { + zip.start_file(file, options)?; + std::io::copy(&mut File::open(path)?, &mut zip)?; + } + Entry::Directory(dir) => { + zip.add_directory(dir, options)?; + } + } + progress.inc(1); } + progress.finish_and_clear(); zip.finish()?; info!("Created release: {}", output.display()); std::fs::copy(&output, { diff --git a/bin/src/modules/binarize/error/bbw1_tools_not_found.rs b/bin/src/modules/binarize/error/bbw1_tools_not_found.rs index 131de792..bb67d7c4 100644 --- a/bin/src/modules/binarize/error/bbw1_tools_not_found.rs +++ b/bin/src/modules/binarize/error/bbw1_tools_not_found.rs @@ -16,13 +16,19 @@ impl Code for ToolsNotFound { } fn message(&self) -> String { - String::from("Arma 3 Tools not found in registry.") + String::from("Arma 3 Tools not found.") } fn help(&self) -> Option { - Some(String::from( - "Install Arma 3 Tools from Steam and run them at least once.", - )) + if cfg!(windows) { + Some(String::from( + "Install Arma 3 Tools from Steam and run them at least once.", + )) + } else { + Some(String::from( + "Install Arma 3 Tools, and ensure either `wine64` or Proton Sniper is installed.", + )) + } } fn diagnostic(&self) -> Option { diff --git a/bin/src/modules/binarize/mod.rs b/bin/src/modules/binarize/mod.rs index 9d583ba2..f4b2863d 100644 --- a/bin/src/modules/binarize/mod.rs +++ b/bin/src/modules/binarize/mod.rs @@ -86,6 +86,7 @@ impl Module for Binarize { #[cfg(not(windows))] fn init(&mut self, ctx: &Context) -> Result { + use dirs::home_dir; use hemtt_common::steam; let mut report = Report::new(); @@ -117,7 +118,17 @@ impl Module for Binarize { let mut cmd = Command::new("wine64"); cmd.arg("--version"); if cmd.output().is_err() { - self.proton = true; + if home_dir() + .expect("home directory exists") + .join(".local/share/Steam/steamapps/common/SteamLinuxRuntime_sniper/run") + .exists() + { + self.proton = true; + } else { + debug!("tools found, but not wine64 or proton"); + report.push(ToolsNotFound::code(Severity::Warning)); + self.command = None; + } } } else { report.push(ToolsNotFound::code(Severity::Warning)); diff --git a/bin/src/modules/files.rs b/bin/src/modules/files.rs index 37b35973..582a8755 100644 --- a/bin/src/modules/files.rs +++ b/bin/src/modules/files.rs @@ -1,6 +1,6 @@ use std::fs::create_dir_all; -use crate::{context::Context, error::Error, report::Report}; +use crate::{context::Context, error::Error, progress::progress_bar, report::Report}; use super::Module; @@ -17,7 +17,7 @@ impl Module for Files { require_literal_separator: true, ..Default::default() }; - let mut copied = 0; + let mut to_copy = Vec::new(); let mut globs = Vec::new(); for file in ctx.config().files().include() { globs.push(glob::Pattern::new(file)?); @@ -47,10 +47,19 @@ impl Module for Files { if !folder.exists() { std::mem::drop(create_dir_all(folder)); } - debug!("copying {:?} => {:?}", entry.as_str(), d.display()); - std::io::copy(&mut entry.open_file()?, &mut std::fs::File::create(&d)?)?; + to_copy.push((entry, d)); + } + + let mut copied = 0; + let progress = progress_bar(to_copy.len() as u64).with_message("Copying files"); + for (source, dest) in to_copy { + debug!("copying {:?} => {:?}", source.as_str(), dest.display()); + progress.set_message(format!("Copying {}", source.as_str())); + std::io::copy(&mut source.open_file()?, &mut std::fs::File::create(&dest)?)?; copied += 1; + progress.inc(1); } + progress.finish_and_clear(); info!("Copied {} files", copied); Ok(Report::new()) } diff --git a/bin/src/modules/mod.rs b/bin/src/modules/mod.rs index 2c1b32e7..bb629f69 100644 --- a/bin/src/modules/mod.rs +++ b/bin/src/modules/mod.rs @@ -1,27 +1,28 @@ use crate::{context::Context, error::Error, report::Report}; -pub mod archive; -pub mod bom; -pub mod hook; -pub mod pbo; - -pub use hook::Hooks; - mod binarize; mod file_patching; mod files; mod new; mod rapifier; -pub(crate) mod sign; mod sqf; +mod stringtables; + +pub mod archive; +pub mod bom; +pub mod hook; +pub mod pbo; +pub(crate) mod sign; pub use binarize::Binarize; pub use file_patching::FilePatching; pub use files::Files; +pub use hook::Hooks; pub use new::Licenses; pub use rapifier::Rapifier; pub use sign::Sign; pub use sqf::SQFCompiler; +pub use stringtables::Stringtables; pub trait Module { fn name(&self) -> &'static str; diff --git a/bin/src/modules/pbo.rs b/bin/src/modules/pbo.rs index b2ea24e1..033e3a7b 100644 --- a/bin/src/modules/pbo.rs +++ b/bin/src/modules/pbo.rs @@ -12,7 +12,7 @@ use hemtt_pbo::WritablePbo; use hemtt_workspace::addons::{Addon, Location}; use vfs::VfsFileType; -use crate::{context::Context, error::Error, report::Report}; +use crate::{context::Context, error::Error, progress::progress_bar, report::Report}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// Should the optional and compat PBOs be collapsed into the addons folder @@ -42,15 +42,18 @@ pub fn build(ctx: &Context, collapse: Collapse) -> Result { }) }; let counter = AtomicU16::new(0); + let progress = progress_bar(ctx.addons().to_vec().len() as u64).with_message("Building PBOs"); ctx.addons() .to_vec() .iter() .map(|addon| { internal_build(ctx, addon, collapse, &version, git_hash.as_ref())?; + progress.inc(1); counter.fetch_add(1, Ordering::Relaxed); Ok(()) }) .collect::, Error>>()?; + progress.finish_and_clear(); info!("Built {} PBOs", counter.load(Ordering::Relaxed)); Ok(Report::new()) } diff --git a/bin/src/modules/rapifier.rs b/bin/src/modules/rapifier.rs index 9b17f681..25b433c8 100644 --- a/bin/src/modules/rapifier.rs +++ b/bin/src/modules/rapifier.rs @@ -3,18 +3,16 @@ use std::{ sync::atomic::{AtomicU16, Ordering}, }; -use hemtt_config::{lint_check, parse, rapify::Rapify}; +use hemtt_config::{analyze::lint_check, parse, rapify::Rapify}; use hemtt_preprocessor::Processor; use hemtt_workspace::{addons::Addon, WorkspacePath}; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use vfs::VfsFileType; -use crate::{context::Context, error::Error, report::Report}; +use crate::{context::Context, error::Error, progress::progress_bar, report::Report}; use super::Module; -// type RapifyResult = (Vec<(String, Vec)>, Result<(), Error>); - #[derive(Default)] pub struct Rapifier; @@ -25,7 +23,7 @@ impl Module for Rapifier { fn check(&self, ctx: &Context) -> Result { let mut report = Report::new(); - report.extend(lint_check(ctx.config())); + report.extend(lint_check(ctx.config().lints().config().clone())); Ok(report) } @@ -68,11 +66,13 @@ impl Module for Rapifier { }) .collect::, Error>>()?; + let progress = progress_bar(entries.len() as u64).with_message("Rapifying Configs"); let reports = entries .par_iter() .map(|(addon, entry)| { let report = rapify(addon, entry, ctx)?; counter.fetch_add(1, Ordering::Relaxed); + progress.inc(1); Ok(report) }) .collect::, Error>>()?; @@ -81,6 +81,7 @@ impl Module for Rapifier { report.merge(new_report); } + progress.finish_and_clear(); info!("Rapified {} addon configs", counter.load(Ordering::Relaxed)); Ok(report) } diff --git a/bin/src/modules/sqf.rs b/bin/src/modules/sqf.rs index cb5f6921..a018fd43 100644 --- a/bin/src/modules/sqf.rs +++ b/bin/src/modules/sqf.rs @@ -12,7 +12,7 @@ use hemtt_sqf::{ use hemtt_workspace::reporting::{Code, CodesExt, Diagnostic, Severity}; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; -use crate::{context::Context, error::Error, report::Report}; +use crate::{context::Context, error::Error, progress::progress_bar, report::Report}; use super::Module; @@ -43,13 +43,7 @@ impl Module for SQFCompiler { fn check(&self, ctx: &Context) -> Result { let mut report = Report::new(); - report.extend(lint_check( - ctx.config(), - self.database - .as_ref() - .expect("database not initialized") - .clone(), - )); + report.extend(lint_check(ctx.config().lints().sqf().clone())); Ok(report) } @@ -75,6 +69,7 @@ impl Module for SQFCompiler { .as_ref() .expect("database not initialized") .clone(); + let progress = progress_bar(entries.len() as u64).with_message("Compiling SQF"); let reports = entries .par_iter() .map(|(addon, entry)| { @@ -97,6 +92,7 @@ impl Module for SQFCompiler { let mut out = entry.with_extension("sqfc")?.create_file()?; sqf.optimize().compile_to_writer(&processed, &mut out)?; counter.fetch_add(1, Ordering::Relaxed); + progress.inc(1); } for code in codes { report.push(code); @@ -127,6 +123,7 @@ impl Module for SQFCompiler { for new_report in reports { report.merge(new_report); } + progress.finish_and_clear(); info!("Compiled {} sqf files", counter.load(Ordering::Relaxed)); Ok(report) } diff --git a/bin/src/modules/stringtables/mod.rs b/bin/src/modules/stringtables/mod.rs new file mode 100644 index 00000000..0cd524aa --- /dev/null +++ b/bin/src/modules/stringtables/mod.rs @@ -0,0 +1,36 @@ +use hemtt_stringtable::analyze::{lint_addons, lint_check}; + +use crate::{context::Context, report::Report, Error}; + +use super::Module; + +#[derive(Debug, Default)] +pub struct Stringtables; +impl Stringtables { + #[must_use] + pub const fn new() -> Self { + Self + } +} + +impl Module for Stringtables { + fn name(&self) -> &'static str { + "Stringtables" + } + + fn check(&self, ctx: &crate::context::Context) -> Result { + let mut report = Report::new(); + report.extend(lint_check(ctx.config().lints().sqf().clone())); + Ok(report) + } + + fn pre_build(&self, ctx: &Context) -> Result { + let mut report = Report::new(); + report.extend(lint_addons( + ctx.workspace_path().to_owned(), + &ctx.addons().to_vec(), + Some(ctx.config()), + )); + Ok(report) + } +} diff --git a/bin/src/progress.rs b/bin/src/progress.rs new file mode 100644 index 00000000..38c44d64 --- /dev/null +++ b/bin/src/progress.rs @@ -0,0 +1,17 @@ +use indicatif::{ProgressBar, ProgressStyle}; + +#[allow(clippy::module_name_repetitions)] +pub fn progress_bar(size: u64) -> ProgressBar { + ProgressBar::new(size).with_style( + ProgressStyle::with_template( + if std::env::var("CI").is_ok() + || std::env::args().any(|a| a.starts_with("-v") && a.ends_with('v')) + { + "" + } else { + "{msg} [{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} " + }, + ) + .expect("valid template"), + ) +} diff --git a/bin/src/report.rs b/bin/src/report.rs index 18185c5a..e371e6e0 100644 --- a/bin/src/report.rs +++ b/bin/src/report.rs @@ -126,8 +126,9 @@ fn filter_codes( true } else { !c.include() - && c.diagnostic() - .map_or(true, |d| !d.labels.iter().all(|l| l.file().is_include())) + && c.diagnostic().map_or(true, |d| { + d.labels.is_empty() || !d.labels.iter().all(|l| l.file().is_include()) + }) } }) .cloned() diff --git a/book-lints/src/main.rs b/book-lints/src/main.rs index f3ccfa3d..a3a05c98 100644 --- a/book-lints/src/main.rs +++ b/book-lints/src/main.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use arma3_wiki::Wiki; -use hemtt_config::CONFIG_LINTS; +use hemtt_config::analyze::CONFIG_LINTS; use hemtt_sqf::analyze::{ lints::s02_event_handlers::{ LintS02EventIncorrectCommand, LintS02EventInsufficientVersion, LintS02EventUnknown, diff --git a/libs/common/src/config/project/lint.rs b/libs/common/src/config/project/lint.rs index 0bafe81e..42b34f64 100644 --- a/libs/common/src/config/project/lint.rs +++ b/libs/common/src/config/project/lint.rs @@ -7,9 +7,9 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq)] /// Lint group config pub struct LintGroupConfig { - /// Lints for config config: HashMap, sqf: HashMap, + stringtables: HashMap, } impl LintGroupConfig { @@ -25,6 +25,12 @@ impl LintGroupConfig { &self.sqf } + #[must_use] + /// Get the stringtables lints + pub const fn stringtables(&self) -> &HashMap { + &self.stringtables + } + pub fn is_empty(&self) -> bool { self.config.is_empty() && self.sqf.is_empty() } @@ -103,7 +109,7 @@ impl LintConfig { } #[allow(clippy::module_name_repetitions)] -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Default, Debug, PartialEq, Deserialize, Serialize)] pub struct LintConfigOverride { enabled: Option, severity: Option, @@ -147,6 +153,7 @@ impl LintConfigOverride { pub struct LintSectionFile { pub config: Option>, pub sqf: Option>, + pub stringtables: Option>, } impl From for LintGroupConfig { @@ -164,6 +171,12 @@ impl From for LintGroupConfig { .into_iter() .map(|(k, v)| (k, v.into())) .collect(), + stringtables: file + .stringtables + .unwrap_or_default() + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(), } } } diff --git a/libs/config/src/analyze/cfgpatch.rs b/libs/config/src/analyze/cfgpatch.rs index 98e5333a..ea5df193 100644 --- a/libs/config/src/analyze/cfgpatch.rs +++ b/libs/config/src/analyze/cfgpatch.rs @@ -9,6 +9,7 @@ pub struct CfgPatch { } impl CfgPatch { + #[must_use] pub const fn new(name: Ident, required_version: Version) -> Self { Self { name, @@ -16,10 +17,12 @@ impl CfgPatch { } } + #[must_use] pub const fn name(&self) -> &Ident { &self.name } + #[must_use] pub const fn required_version(&self) -> &Version { &self.required_version } diff --git a/libs/config/src/analyze/chumsky.rs b/libs/config/src/analyze/chumsky.rs index 495256e7..333d7739 100644 --- a/libs/config/src/analyze/chumsky.rs +++ b/libs/config/src/analyze/chumsky.rs @@ -24,6 +24,7 @@ impl Code for ChumskyCode { } impl ChumskyCode { + #[must_use] pub fn new(err: Simple, processed: &Processed) -> Self { Self { err, @@ -33,7 +34,7 @@ impl ChumskyCode { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.err.span(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.err.span(), processed); if let Some(diag) = &mut self.diagnostic { diag.notes.push(format!( "The processed output of the line with the error was:\n{} ", diff --git a/libs/config/src/analyze/lints/c01_invalid_value.rs b/libs/config/src/analyze/lints/c01_invalid_value.rs index 916abee9..9935c45c 100644 --- a/libs/config/src/analyze/lints/c01_invalid_value.rs +++ b/libs/config/src/analyze/lints/c01_invalid_value.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{Code, Codes, Diagnostic, Processed}, }; -use crate::{Item, Value}; +use crate::{analyze::SqfLintData, Item, Value}; -crate::lint!(LintC01InvalidValue); +crate::analyze::lint!(LintC01InvalidValue); -impl Lint<()> for LintC01InvalidValue { +impl Lint for LintC01InvalidValue { fn ident(&self) -> &str { "invalid_value" } @@ -49,14 +49,14 @@ Arma configs only support Strings, Numbers, and Arrays. While other tools would LintConfig::error() } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![Box::new(RunnerValue), Box::new(RunnerItem)] } } struct RunnerValue; -impl LintRunner<()> for RunnerValue { +impl LintRunner for RunnerValue { type Target = Value; fn run( &self, @@ -64,7 +64,7 @@ impl LintRunner<()> for RunnerValue { _config: &LintConfig, processed: Option<&Processed>, target: &Value, - _data: &(), + _data: &SqfLintData, ) -> Codes { let Some(processed) = processed else { return vec![]; @@ -85,7 +85,7 @@ impl LintRunner<()> for RunnerValue { } struct RunnerItem; -impl LintRunner<()> for RunnerItem { +impl LintRunner for RunnerItem { type Target = Item; fn run( &self, @@ -93,7 +93,7 @@ impl LintRunner<()> for RunnerItem { _config: &LintConfig, processed: Option<&Processed>, target: &Item, - _data: &(), + _data: &SqfLintData, ) -> Codes { let Some(processed) = processed else { return vec![]; @@ -156,6 +156,7 @@ impl Code for CodeC01InvalidValue { } impl CodeC01InvalidValue { + #[must_use] pub fn new(span: Range, processed: &Processed) -> Self { Self { value: processed.as_str()[span.clone()].to_string(), @@ -166,7 +167,7 @@ impl CodeC01InvalidValue { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); self } } @@ -203,6 +204,7 @@ impl Code for CodeC01InvalidValueMacro { } impl CodeC01InvalidValueMacro { + #[must_use] pub fn new(span: Range, processed: &Processed) -> Self { Self { span, @@ -212,7 +214,7 @@ impl CodeC01InvalidValueMacro { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); if let Some(diag) = &mut self.diagnostic { diag.notes.push(format!( "The processed output was:\n{} ", diff --git a/libs/config/src/analyze/lints/c02_duplicate_property.rs b/libs/config/src/analyze/lints/c02_duplicate_property.rs index 35d83157..b79223e4 100644 --- a/libs/config/src/analyze/lints/c02_duplicate_property.rs +++ b/libs/config/src/analyze/lints/c02_duplicate_property.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{Code, Codes, Diagnostic, Label, Processed}, }; -use crate::{Class, Config, Ident, Property}; +use crate::{analyze::SqfLintData, Class, Config, Ident, Property}; -crate::lint!(LintC02DuplicateProperty); +crate::analyze::lint!(LintC02DuplicateProperty); -impl Lint<()> for LintC02DuplicateProperty { +impl Lint for LintC02DuplicateProperty { fn ident(&self) -> &str { "duplicate_property" } @@ -54,13 +54,13 @@ Properties on a class must be unique, regardless of the type of property. LintConfig::error() } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![Box::new(Runner)] } } struct Runner; -impl LintRunner<()> for Runner { +impl LintRunner for Runner { type Target = Config; fn run( &self, @@ -68,7 +68,7 @@ impl LintRunner<()> for Runner { _config: &LintConfig, processed: Option<&Processed>, target: &Config, - _data: &(), + _data: &SqfLintData, ) -> Codes { let Some(processed) = processed else { return vec![]; @@ -148,6 +148,7 @@ impl Code for CodeC02DuplicateProperty { } impl CodeC02DuplicateProperty { + #[must_use] pub fn new(conflicts: Vec, processed: &Processed) -> Self { Self { conflicts, @@ -157,7 +158,7 @@ impl CodeC02DuplicateProperty { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed( + self.diagnostic = Diagnostic::from_code_processed( &self, self.conflicts .last() diff --git a/libs/config/src/analyze/lints/c03_duplicate_classes.rs b/libs/config/src/analyze/lints/c03_duplicate_classes.rs index 3763799e..212f4841 100644 --- a/libs/config/src/analyze/lints/c03_duplicate_classes.rs +++ b/libs/config/src/analyze/lints/c03_duplicate_classes.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{Code, Codes, Diagnostic, Label, Processed}, }; -use crate::{Class, Config, Property}; +use crate::{analyze::SqfLintData, Class, Config, Property}; -crate::lint!(LintC03DuplicateClasses); +crate::analyze::lint!(LintC03DuplicateClasses); -impl Lint<()> for LintC03DuplicateClasses { +impl Lint for LintC03DuplicateClasses { fn ident(&self) -> &str { "duplicate_classes" } @@ -58,13 +58,13 @@ Children classes can only be defined once in a class. LintConfig::error() } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![Box::new(Runner)] } } struct Runner; -impl LintRunner<()> for Runner { +impl LintRunner for Runner { type Target = Config; fn run( &self, @@ -72,7 +72,7 @@ impl LintRunner<()> for Runner { _config: &LintConfig, processed: Option<&Processed>, target: &Config, - _data: &(), + _data: &SqfLintData, ) -> Codes { let Some(processed) = processed else { return vec![]; @@ -81,6 +81,7 @@ impl LintRunner<()> for Runner { } } +#[must_use] pub fn check(properties: &[Property], processed: &Processed) -> Codes { let mut defined: HashMap> = HashMap::new(); let mut codes = Vec::new(); @@ -163,6 +164,7 @@ impl Code for CodeC03DuplicateClasses { } impl CodeC03DuplicateClasses { + #[must_use] pub fn new(classes: Vec, processed: &Processed) -> Self { Self { classes, @@ -175,7 +177,7 @@ impl CodeC03DuplicateClasses { let Some(name) = self.classes[0].name() else { panic!("CodeC03DuplicateClasses::generate_processed called on class without name"); }; - self.diagnostic = Diagnostic::new_for_processed(&self, name.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, name.span.clone(), processed); if let Some(diag) = &mut self.diagnostic { for class in self.classes.iter().skip(1) { let map = processed diff --git a/libs/config/src/analyze/lints/c04_external_missing.rs b/libs/config/src/analyze/lints/c04_external_missing.rs index 673d7145..cc79079a 100644 --- a/libs/config/src/analyze/lints/c04_external_missing.rs +++ b/libs/config/src/analyze/lints/c04_external_missing.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{Code, Codes, Diagnostic, Processed}, }; -use crate::{Class, Config, Property}; +use crate::{analyze::SqfLintData, Class, Config, Property}; -crate::lint!(LintC04ExternalMissing); +crate::analyze::lint!(LintC04ExternalMissing); -impl Lint<()> for LintC04ExternalMissing { +impl Lint for LintC04ExternalMissing { fn ident(&self) -> &str { "external_missing" } @@ -53,13 +53,13 @@ Read more about [class inheritance](https://community.bistudio.com/wiki/Class_In LintConfig::error() } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![Box::new(Runner)] } } struct Runner; -impl LintRunner<()> for Runner { +impl LintRunner for Runner { type Target = Config; fn run( &self, @@ -67,7 +67,7 @@ impl LintRunner<()> for Runner { _config: &LintConfig, processed: Option<&Processed>, target: &Config, - _data: &(), + _data: &SqfLintData, ) -> Vec> { let Some(processed) = processed else { return vec![]; @@ -148,6 +148,7 @@ impl Code for CodeC04ExternalMissing { } impl CodeC04ExternalMissing { + #[must_use] pub fn new(class: Class, processed: &Processed) -> Self { Self { class, @@ -160,7 +161,7 @@ impl CodeC04ExternalMissing { let Some(parent) = self.class.parent() else { panic!("CodeC04ExternalMissing::generate_processed called on class without parent"); }; - self.diagnostic = Diagnostic::new_for_processed(&self, parent.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, parent.span.clone(), processed); self } } diff --git a/libs/config/src/analyze/lints/c05_external_parent_case.rs b/libs/config/src/analyze/lints/c05_external_parent_case.rs index c8a63778..19d9bed5 100644 --- a/libs/config/src/analyze/lints/c05_external_parent_case.rs +++ b/libs/config/src/analyze/lints/c05_external_parent_case.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{Code, Codes, Diagnostic, Label, Processed, Severity}, }; -use crate::{Class, Property}; +use crate::{analyze::SqfLintData, Class, Property}; -crate::lint!(LintC05ExternalParentCase); +crate::analyze::lint!(LintC05ExternalParentCase); -impl Lint<()> for LintC05ExternalParentCase { +impl Lint for LintC05ExternalParentCase { fn ident(&self) -> &str { "external_parent_case" } @@ -53,13 +53,13 @@ While Arma does not care about the case of class names, HEMTT wants you to have Severity::Help } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![Box::new(Runner)] } } struct Runner; -impl LintRunner<()> for Runner { +impl LintRunner for Runner { type Target = Class; fn run( &self, @@ -67,7 +67,7 @@ impl LintRunner<()> for Runner { _config: &LintConfig, processed: Option<&Processed>, target: &Class, - _data: &(), + _data: &SqfLintData, ) -> Vec> { let Some(processed) = processed else { return vec![]; @@ -171,6 +171,7 @@ impl Code for Code05ExternalParentCase { } impl Code05ExternalParentCase { + #[must_use] pub fn new(class: Class, parent: Class, processed: &Processed) -> Self { Self { class, @@ -182,7 +183,7 @@ impl Code05ExternalParentCase { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed( + self.diagnostic = Diagnostic::from_code_processed( &self, self.class .parent() diff --git a/libs/config/src/analyze/lints/c06_unexpected_array.rs b/libs/config/src/analyze/lints/c06_unexpected_array.rs index d82512dc..20a08df8 100644 --- a/libs/config/src/analyze/lints/c06_unexpected_array.rs +++ b/libs/config/src/analyze/lints/c06_unexpected_array.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{Code, Diagnostic, Label, Processed}, }; -use crate::{Property, Value}; +use crate::{analyze::SqfLintData, Property, Value}; -crate::lint!(LintC06UnexpectedArray); +crate::analyze::lint!(LintC06UnexpectedArray); -impl Lint<()> for LintC06UnexpectedArray { +impl Lint for LintC06UnexpectedArray { fn ident(&self) -> &str { "unexpected_array" } @@ -50,13 +50,13 @@ Arrays in Arma configs are denoted by `[]` after the property name. LintConfig::error() } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![Box::new(Runner)] } } struct Runner; -impl LintRunner<()> for Runner { +impl LintRunner for Runner { type Target = crate::Property; fn run( &self, @@ -64,7 +64,7 @@ impl LintRunner<()> for Runner { _config: &LintConfig, processed: Option<&Processed>, target: &crate::Property, - _data: &(), + _data: &SqfLintData, ) -> Vec> { let Some(processed) = processed else { return vec![]; @@ -120,6 +120,7 @@ impl Code for Code06UnexpectedArray { } impl Code06UnexpectedArray { + #[must_use] pub fn new(property: Property, processed: &Processed) -> Self { Self { property, @@ -151,7 +152,7 @@ impl Code06UnexpectedArray { .mapping(name.span.end) .expect("mapping should exist"); self.suggestion = Some(format!("{}[]", name.value)); - self.diagnostic = Diagnostic::new_for_processed( + self.diagnostic = Diagnostic::from_code_processed( &self, ident_start.original_start()..ident_end.original_start(), processed, diff --git a/libs/config/src/analyze/lints/c07_expected_array.rs b/libs/config/src/analyze/lints/c07_expected_array.rs index 6e1a7abf..c73dc5a7 100644 --- a/libs/config/src/analyze/lints/c07_expected_array.rs +++ b/libs/config/src/analyze/lints/c07_expected_array.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{Code, Diagnostic, Label, Processed}, }; -use crate::{Property, Value}; +use crate::{analyze::SqfLintData, Property, Value}; -crate::lint!(LintC07ExpectedArray); +crate::analyze::lint!(LintC07ExpectedArray); -impl Lint<()> for LintC07ExpectedArray { +impl Lint for LintC07ExpectedArray { fn ident(&self) -> &str { "expected_array" } @@ -50,13 +50,13 @@ Only properties that are arrays must have `[]` after the property name. LintConfig::error() } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![Box::new(Runner)] } } struct Runner; -impl LintRunner<()> for Runner { +impl LintRunner for Runner { type Target = crate::Property; fn run( &self, @@ -64,7 +64,7 @@ impl LintRunner<()> for Runner { _config: &LintConfig, processed: Option<&Processed>, target: &crate::Property, - _data: &(), + _data: &SqfLintData, ) -> Vec> { let Some(processed) = processed else { return vec![]; @@ -131,6 +131,7 @@ impl Code for Code07ExpectedArray { } impl Code07ExpectedArray { + #[must_use] pub fn new(property: Property, processed: &Processed) -> Self { Self { property, @@ -168,7 +169,7 @@ impl Code07ExpectedArray { let haystack = &processed.as_str()[ident_end.original_start()..value.span().start]; let possible_end = ident_end.original_start() + haystack.find(']').unwrap_or(1) + 1; self.suggestion = Some(name.value.to_string()); - self.diagnostic = Diagnostic::new_for_processed( + self.diagnostic = Diagnostic::from_code_processed( &self, ident_start.original_start()..possible_end, processed, diff --git a/libs/config/src/analyze/lints/c08_missing_semicolon.rs b/libs/config/src/analyze/lints/c08_missing_semicolon.rs index 4e49fddc..152acf6b 100644 --- a/libs/config/src/analyze/lints/c08_missing_semicolon.rs +++ b/libs/config/src/analyze/lints/c08_missing_semicolon.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{diagnostic::Yellow, Code, Diagnostic, Processed}, }; -use crate::Property; +use crate::{analyze::SqfLintData, Property}; -crate::lint!(LintC08MissingSemicolon); +crate::analyze::lint!(LintC08MissingSemicolon); -impl Lint<()> for LintC08MissingSemicolon { +impl Lint for LintC08MissingSemicolon { fn ident(&self) -> &str { "missing_semicolon" } @@ -64,14 +64,14 @@ All properties must end with a semicolon, including classes. LintConfig::error() } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![Box::new(Runner)] } } struct Runner; -impl LintRunner<()> for Runner { +impl LintRunner for Runner { type Target = crate::Property; fn run( &self, @@ -79,7 +79,7 @@ impl LintRunner<()> for Runner { _config: &LintConfig, processed: Option<&Processed>, target: &crate::Property, - _data: &(), + _data: &SqfLintData, ) -> Vec> { let Some(processed) = processed else { return vec![]; @@ -130,6 +130,7 @@ impl Code for Code08MissingSemicolon { } impl Code08MissingSemicolon { + #[must_use] pub fn new(span: Range, processed: &Processed) -> Self { Self { span, @@ -145,7 +146,7 @@ impl Code08MissingSemicolon { .find('\n') .unwrap_or_else(|| haystack.rfind(|c: char| c != ' ' && c != '}').unwrap_or(0) + 1); self.diagnostic = - Diagnostic::new_for_processed(&self, possible_end..possible_end, processed); + Diagnostic::from_code_processed(&self, possible_end..possible_end, processed); self } } diff --git a/libs/config/src/analyze/lints/c09_magwell_missing_magazine.rs b/libs/config/src/analyze/lints/c09_magwell_missing_magazine.rs index 098e789a..37a630ca 100644 --- a/libs/config/src/analyze/lints/c09_magwell_missing_magazine.rs +++ b/libs/config/src/analyze/lints/c09_magwell_missing_magazine.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{Code, Codes, Diagnostic, Label, Processed, Severity}, }; -use crate::{Class, Config, Ident, Item, Property, Str, Value}; +use crate::{analyze::SqfLintData, Class, Config, Ident, Item, Property, Str, Value}; -crate::lint!(LintC09MagwellMissingMagazine); +crate::analyze::lint!(LintC09MagwellMissingMagazine); -impl Lint<()> for LintC09MagwellMissingMagazine { +impl Lint for LintC09MagwellMissingMagazine { fn ident(&self) -> &str { "magwell_missing_magazine" } @@ -74,13 +74,13 @@ Magazines defined in `CfgMagazineWells` that are using the project's prefix (abe Severity::Warning } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![Box::new(Runner)] } } struct Runner; -impl LintRunner<()> for Runner { +impl LintRunner for Runner { type Target = Config; fn run( &self, @@ -88,7 +88,7 @@ impl LintRunner<()> for Runner { _config: &LintConfig, processed: Option<&Processed>, target: &Config, - _data: &(), + _data: &SqfLintData, ) -> Codes { let Some(processed) = processed else { return vec![]; @@ -194,6 +194,7 @@ impl Code for Code09MagwellMissingMagazine { } impl Code09MagwellMissingMagazine { + #[must_use] pub fn new(array: Ident, span: Range, processed: &Processed) -> Self { Self { array, @@ -205,7 +206,7 @@ impl Code09MagwellMissingMagazine { } fn diagnostic_generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); if let Some(diag) = &mut self.diagnostic { diag.labels.push({ let Some(map) = processed.mapping(self.array.span.start) else { diff --git a/libs/config/src/analyze/lints/c10_class_missing_braces.rs b/libs/config/src/analyze/lints/c10_class_missing_braces.rs index 87c0d4de..56d55eab 100644 --- a/libs/config/src/analyze/lints/c10_class_missing_braces.rs +++ b/libs/config/src/analyze/lints/c10_class_missing_braces.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{Code, Diagnostic, Processed}, }; -use crate::{Class, Property}; +use crate::{analyze::SqfLintData, Class, Property}; -crate::lint!(LintC10ClassMissingBraces); +crate::analyze::lint!(LintC10ClassMissingBraces); -impl Lint<()> for LintC10ClassMissingBraces { +impl Lint for LintC10ClassMissingBraces { fn ident(&self) -> &str { "class_missing_braces" } @@ -54,14 +54,14 @@ All classes using inheritance with a parent class must use braces `{}`, even if LintConfig::error() } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![Box::new(Runner)] } } struct Runner; -impl LintRunner<()> for Runner { +impl LintRunner for Runner { type Target = crate::Property; fn run( &self, @@ -69,7 +69,7 @@ impl LintRunner<()> for Runner { _config: &LintConfig, processed: Option<&Processed>, target: &crate::Property, - _data: &(), + _data: &SqfLintData, ) -> Vec> { let Some(processed) = processed else { return vec![]; @@ -118,6 +118,7 @@ impl Code for Code10ClassMissingBraces { } impl Code10ClassMissingBraces { + #[must_use] pub fn new(span: Range, processed: &Processed) -> Self { Self { span, @@ -133,7 +134,7 @@ impl Code10ClassMissingBraces { .find('\n') .unwrap_or_else(|| haystack.rfind(|c: char| c != ' ' && c != '}').unwrap_or(0) + 1); self.diagnostic = - Diagnostic::new_for_processed(&self, possible_end..possible_end, processed); + Diagnostic::from_code_processed(&self, possible_end..possible_end, processed); self } } diff --git a/libs/config/src/analyze/mod.rs b/libs/config/src/analyze/mod.rs index 47414d86..80fe7832 100644 --- a/libs/config/src/analyze/mod.rs +++ b/libs/config/src/analyze/mod.rs @@ -1,6 +1,7 @@ use hemtt_common::config::ProjectConfig; use hemtt_workspace::{ - lint::{Lint, LintManager}, + lint::LintManager, + lint_manager, reporting::{Codes, Processed}, }; @@ -11,19 +12,9 @@ pub mod lints { automod::dir!(pub "src/analyze/lints"); } -#[linkme::distributed_slice] -pub static CONFIG_LINTS: [std::sync::LazyLock>>>]; +pub struct SqfLintData {} -#[macro_export] -macro_rules! lint { - ($name:ident) => { - #[allow(clippy::module_name_repetitions)] - pub struct $name; - #[linkme::distributed_slice($crate::analyze::CONFIG_LINTS)] - static LINT_ADD: std::sync::LazyLock>>> = - std::sync::LazyLock::new(|| std::sync::Arc::new(Box::new($name))); - }; -} +lint_manager!(config, vec![]); pub use cfgpatch::CfgPatch; pub use chumsky::ChumskyCode; @@ -34,12 +25,13 @@ use crate::{Array, Class, Config, Expression, Item, Number, Property, Str, Value pub trait Analyze: Sized + 'static { fn analyze( &self, + data: &SqfLintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager<()>, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; - codes.extend(manager.run(project, Some(processed), self)); + codes.extend(manager.run(data, project, Some(processed), self)); codes } } @@ -51,17 +43,18 @@ impl Analyze for Expression {} impl Analyze for Config { fn analyze( &self, + data: &SqfLintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager<()>, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; - codes.extend(manager.run(project, Some(processed), self)); - codes.extend(manager.run(project, Some(processed), &self.to_class())); + codes.extend(manager.run(data, project, Some(processed), self)); + codes.extend(manager.run(data, project, Some(processed), &self.to_class())); codes.extend( self.0 .iter() - .flat_map(|p| p.analyze(project, processed, manager)), + .flat_map(|p| p.analyze(data, project, processed, manager)), ); codes } @@ -70,17 +63,18 @@ impl Analyze for Config { impl Analyze for Class { fn analyze( &self, + data: &SqfLintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager<()>, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; - codes.extend(manager.run(project, Some(processed), self)); + codes.extend(manager.run(data, project, Some(processed), self)); codes.extend(match self { Self::External { .. } => vec![], Self::Local { properties, .. } | Self::Root { properties, .. } => properties .iter() - .flat_map(|p| p.analyze(project, processed, manager)) + .flat_map(|p| p.analyze(data, project, processed, manager)) .collect::>(), }); codes @@ -90,15 +84,16 @@ impl Analyze for Class { impl Analyze for Property { fn analyze( &self, + data: &SqfLintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager<()>, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; - codes.extend(manager.run(project, Some(processed), self)); + codes.extend(manager.run(data, project, Some(processed), self)); codes.extend(match self { - Self::Entry { value, .. } => value.analyze(project, processed, manager), - Self::Class(c) => c.analyze(project, processed, manager), + Self::Entry { value, .. } => value.analyze(data, project, processed, manager), + Self::Class(c) => c.analyze(data, project, processed, manager), Self::Delete(_) | Self::MissingSemicolon(_, _) => vec![], }); codes @@ -108,17 +103,20 @@ impl Analyze for Property { impl Analyze for Value { fn analyze( &self, + data: &SqfLintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager<()>, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; - codes.extend(manager.run(project, Some(processed), self)); + codes.extend(manager.run(data, project, Some(processed), self)); codes.extend(match self { - Self::Str(s) => s.analyze(project, processed, manager), - Self::Number(n) => n.analyze(project, processed, manager), - Self::Expression(e) => e.analyze(project, processed, manager), - Self::Array(a) | Self::UnexpectedArray(a) => a.analyze(project, processed, manager), + Self::Str(s) => s.analyze(data, project, processed, manager), + Self::Number(n) => n.analyze(data, project, processed, manager), + Self::Expression(e) => e.analyze(data, project, processed, manager), + Self::Array(a) | Self::UnexpectedArray(a) => { + a.analyze(data, project, processed, manager) + } Self::Invalid(_) => { vec![] } @@ -130,16 +128,17 @@ impl Analyze for Value { impl Analyze for Array { fn analyze( &self, + data: &SqfLintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager<()>, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; - codes.extend(manager.run(project, Some(processed), self)); + codes.extend(manager.run(data, project, Some(processed), self)); codes.extend( self.items .iter() - .flat_map(|i| i.analyze(project, processed, manager)), + .flat_map(|i| i.analyze(data, project, processed, manager)), ); codes } @@ -148,18 +147,19 @@ impl Analyze for Array { impl Analyze for Item { fn analyze( &self, + data: &SqfLintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager<()>, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; - codes.extend(manager.run(project, Some(processed), self)); + codes.extend(manager.run(data, project, Some(processed), self)); codes.extend(match self { - Self::Str(s) => s.analyze(project, processed, manager), - Self::Number(n) => n.analyze(project, processed, manager), + Self::Str(s) => s.analyze(data, project, processed, manager), + Self::Number(n) => n.analyze(data, project, processed, manager), Self::Array(a) => a .iter() - .flat_map(|i| i.analyze(project, processed, manager)) + .flat_map(|i| i.analyze(data, project, processed, manager)) .collect::>(), Self::Invalid(_) => { vec![] diff --git a/libs/config/src/lib.rs b/libs/config/src/lib.rs index 2d6ddaa5..48d33d2b 100644 --- a/libs/config/src/lib.rs +++ b/libs/config/src/lib.rs @@ -6,15 +6,13 @@ use std::sync::Arc; -mod analyze; +pub mod analyze; mod model; pub mod parse; pub mod rapify; pub use model::*; -pub use analyze::CONFIG_LINTS; - -use analyze::{Analyze, CfgPatch, ChumskyCode}; +use analyze::{Analyze, CfgPatch, ChumskyCode, SqfLintData}; use chumsky::Parser; use hemtt_common::version::Version; @@ -24,20 +22,6 @@ use hemtt_workspace::{ reporting::{Code, Codes, Processed, Severity}, }; -#[must_use] -pub fn lint_check(project: &ProjectConfig) -> Codes { - let mut manager = LintManager::new(project.lints().config().clone(), ()); - if let Err(e) = manager.extend( - CONFIG_LINTS - .iter() - .map(|l| (**l).clone()) - .collect::>(), - ) { - return e; - } - vec![] -} - /// Parse a config file /// /// # Errors @@ -60,16 +44,15 @@ pub fn parse( |config| { let mut manager = LintManager::new( project.map_or_else(Default::default, |project| project.lints().config().clone()), - (), ); manager.extend( - CONFIG_LINTS + analyze::CONFIG_LINTS .iter() .map(|l| (**l).clone()) .collect::>(), )?; Ok(ConfigReport { - codes: config.analyze(project, processed, &manager), + codes: config.analyze(&SqfLintData {}, project, processed, &manager), patches: config.get_patches(), config, }) diff --git a/libs/p3d/src/functions/missing.rs b/libs/p3d/src/functions/missing.rs index 8567d902..7a58fd16 100644 --- a/libs/p3d/src/functions/missing.rs +++ b/libs/p3d/src/functions/missing.rs @@ -68,7 +68,7 @@ impl P3D { } else { format!("\\{texture}") }; - let texture = texture.to_lowercase(); + // let texture = texture.to_lowercase(); if let Some(exists) = cache.exists(&texture) { if !exists { missing_textures.push(texture); diff --git a/libs/sqf/src/analyze/lints/s01_command_required_version.rs b/libs/sqf/src/analyze/lints/s01_command_required_version.rs index 9dca5d89..ef09a37b 100644 --- a/libs/sqf/src/analyze/lints/s01_command_required_version.rs +++ b/libs/sqf/src/analyze/lints/s01_command_required_version.rs @@ -10,7 +10,7 @@ use hemtt_workspace::{ use crate::{analyze::SqfLintData, Statements}; -crate::lint!(LintS01CommandRequiredVersion); +crate::analyze::lint!(LintS01CommandRequiredVersion); impl Lint for LintS01CommandRequiredVersion { fn ident(&self) -> &str { @@ -171,7 +171,7 @@ impl CodeS01CommandRequiredVersion { } fn generate_processed(mut self, processed: &Processed) -> Self { - let Some(diag) = Diagnostic::new_for_processed(&self, self.span.clone(), processed) else { + let Some(diag) = Diagnostic::from_code_processed(&self, self.span.clone(), processed) else { return self; }; self.diagnostic = Some(diag.with_label( diff --git a/libs/sqf/src/analyze/lints/s02_event_handlers.rs b/libs/sqf/src/analyze/lints/s02_event_handlers.rs index 2048a538..9fe70894 100644 --- a/libs/sqf/src/analyze/lints/s02_event_handlers.rs +++ b/libs/sqf/src/analyze/lints/s02_event_handlers.rs @@ -435,7 +435,7 @@ impl CodeS02UnknownEvent { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); self } } @@ -559,7 +559,7 @@ impl CodeS02IncorrectCommand { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); self } } @@ -636,7 +636,7 @@ impl CodeS02InsufficientVersion { } fn generate_processed(mut self, processed: &Processed) -> Self { - let Some(diag) = Diagnostic::new_for_processed(&self, self.span.clone(), processed) else { + let Some(diag) = Diagnostic::from_code_processed(&self, self.span.clone(), processed) else { return self; }; self.diagnostic = Some(diag.with_label( diff --git a/libs/sqf/src/analyze/lints/s03_static_typename.rs b/libs/sqf/src/analyze/lints/s03_static_typename.rs index 1a996b53..695986d4 100644 --- a/libs/sqf/src/analyze/lints/s03_static_typename.rs +++ b/libs/sqf/src/analyze/lints/s03_static_typename.rs @@ -6,7 +6,7 @@ use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, use crate::{analyze::SqfLintData, Expression, NularCommand, UnaryCommand}; -crate::lint!(LintS03StaticTypename); +crate::analyze::lint!(LintS03StaticTypename); impl Lint for LintS03StaticTypename { fn ident(&self) -> &str { @@ -180,7 +180,7 @@ impl CodeS03StaticTypename { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); self } } diff --git a/libs/sqf/src/analyze/lints/s04_command_case.rs b/libs/sqf/src/analyze/lints/s04_command_case.rs index 90d33d00..d0ad6faa 100644 --- a/libs/sqf/src/analyze/lints/s04_command_case.rs +++ b/libs/sqf/src/analyze/lints/s04_command_case.rs @@ -5,7 +5,7 @@ use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, use crate::{analyze::SqfLintData, Expression}; -crate::lint!(LintS04CommandCase); +crate::analyze::lint!(LintS04CommandCase); impl Lint for LintS04CommandCase { fn ident(&self) -> &str { @@ -156,7 +156,7 @@ impl CodeS04CommandCase { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); self } } diff --git a/libs/sqf/src/analyze/lints/s05_if_assign.rs b/libs/sqf/src/analyze/lints/s05_if_assign.rs index 75768fe7..698ff204 100644 --- a/libs/sqf/src/analyze/lints/s05_if_assign.rs +++ b/libs/sqf/src/analyze/lints/s05_if_assign.rs @@ -5,7 +5,7 @@ use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, use crate::{analyze::{extract_constant, SqfLintData}, BinaryCommand, Expression, UnaryCommand}; -crate::lint!(LintS05IfAssign); +crate::analyze::lint!(LintS05IfAssign); impl Lint for LintS05IfAssign { fn ident(&self) -> &str { @@ -205,7 +205,7 @@ impl CodeS05IfAssign { let haystack = &processed.as_str()[self.rhs.1.end..]; let end_position = self.rhs.1.end + haystack.find('}').unwrap_or(0) + 1; self.diagnostic = - Diagnostic::new_for_processed(&self, self.if_cmd.start..end_position, processed); + Diagnostic::from_code_processed(&self, self.if_cmd.start..end_position, processed); self } } diff --git a/libs/sqf/src/analyze/lints/s06_find_in_str.rs b/libs/sqf/src/analyze/lints/s06_find_in_str.rs index 0dd1f67a..a14f0539 100644 --- a/libs/sqf/src/analyze/lints/s06_find_in_str.rs +++ b/libs/sqf/src/analyze/lints/s06_find_in_str.rs @@ -6,7 +6,7 @@ use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, use crate::{analyze::SqfLintData, BinaryCommand, Expression, UnaryCommand}; -crate::lint!(LintS06FindInStr); +crate::analyze::lint!(LintS06FindInStr); impl Lint for LintS06FindInStr { fn ident(&self) -> &str { @@ -167,7 +167,7 @@ impl CodeS06FindInStr { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); self } } diff --git a/libs/sqf/src/analyze/lints/s07_select_parse_number.rs b/libs/sqf/src/analyze/lints/s07_select_parse_number.rs index d6c39398..ac24c275 100644 --- a/libs/sqf/src/analyze/lints/s07_select_parse_number.rs +++ b/libs/sqf/src/analyze/lints/s07_select_parse_number.rs @@ -7,7 +7,7 @@ use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, use crate::{analyze::SqfLintData, parser::database::Database, BinaryCommand, Expression}; -crate::lint!(LintS07SelectParseNumber); +crate::analyze::lint!(LintS07SelectParseNumber); impl Lint for LintS07SelectParseNumber { fn ident(&self) -> &str { @@ -253,7 +253,7 @@ impl CodeS07SelectParseNumber { #[allow(clippy::range_plus_one)] fn generate_processed(mut self, processed: &Processed) -> Self { self.diagnostic = - Diagnostic::new_for_processed(&self, self.span.start..self.span.end + 1, processed); + Diagnostic::from_code_processed(&self, self.span.start..self.span.end + 1, processed); self } } diff --git a/libs/sqf/src/analyze/lints/s08_format_args.rs b/libs/sqf/src/analyze/lints/s08_format_args.rs index ae908adb..d95e961f 100644 --- a/libs/sqf/src/analyze/lints/s08_format_args.rs +++ b/libs/sqf/src/analyze/lints/s08_format_args.rs @@ -5,7 +5,7 @@ use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, use crate::{analyze::SqfLintData, Expression, UnaryCommand}; -crate::lint!(LintS08FormatArgs); +crate::analyze::lint!(LintS08FormatArgs); impl Lint for LintS08FormatArgs { fn ident(&self) -> &str { @@ -205,7 +205,7 @@ impl CodeS08FormatArgs { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); self } } diff --git a/libs/sqf/src/analyze/lints/s09_banned_command.rs b/libs/sqf/src/analyze/lints/s09_banned_command.rs index 18f2e051..26dfda1c 100644 --- a/libs/sqf/src/analyze/lints/s09_banned_command.rs +++ b/libs/sqf/src/analyze/lints/s09_banned_command.rs @@ -5,7 +5,7 @@ use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, use crate::{analyze::SqfLintData, Expression}; -crate::lint!(LintS09BannedCommand); +crate::analyze::lint!(LintS09BannedCommand); impl Lint for LintS09BannedCommand { fn ident(&self) -> &str { @@ -170,7 +170,7 @@ impl CodeS09BannedCommand { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); self } } diff --git a/libs/sqf/src/analyze/lints/s11_if_not_else.rs b/libs/sqf/src/analyze/lints/s11_if_not_else.rs index 4dbd95e9..edc00e88 100644 --- a/libs/sqf/src/analyze/lints/s11_if_not_else.rs +++ b/libs/sqf/src/analyze/lints/s11_if_not_else.rs @@ -8,7 +8,7 @@ use hemtt_workspace::{ use crate::{analyze::SqfLintData, BinaryCommand, Expression, UnaryCommand}; -crate::lint!(LintS11IfNotElse); +crate::analyze::lint!(LintS11IfNotElse); impl Lint for LintS11IfNotElse { fn ident(&self) -> &str { @@ -130,7 +130,7 @@ impl CodeS11IfNot { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); self } } diff --git a/libs/sqf/src/analyze/lints/s17_var_all_caps.rs b/libs/sqf/src/analyze/lints/s17_var_all_caps.rs index 3f75a5b4..f960e9fb 100644 --- a/libs/sqf/src/analyze/lints/s17_var_all_caps.rs +++ b/libs/sqf/src/analyze/lints/s17_var_all_caps.rs @@ -8,7 +8,7 @@ use hemtt_workspace::{ use crate::{analyze::SqfLintData, Expression}; -crate::lint!(LintS17VarAllCaps); +crate::analyze::lint!(LintS17VarAllCaps); impl Lint for LintS17VarAllCaps { fn ident(&self) -> &str { @@ -161,7 +161,7 @@ impl CodeS17VarAllCaps { } fn generate_processed(mut self, processed: &Processed) -> Self { - let Some(mut diagnostic) = Diagnostic::new_for_processed(&self, self.span.clone(), processed) else { + let Some(mut diagnostic) = Diagnostic::from_code_processed(&self, self.span.clone(), processed) else { return self; }; self.diagnostic = Some(diagnostic.clone()); diff --git a/libs/sqf/src/analyze/lints/s18_in_vehicle_check.rs b/libs/sqf/src/analyze/lints/s18_in_vehicle_check.rs index 9a25bcd0..d2f30d11 100644 --- a/libs/sqf/src/analyze/lints/s18_in_vehicle_check.rs +++ b/libs/sqf/src/analyze/lints/s18_in_vehicle_check.rs @@ -8,7 +8,7 @@ use hemtt_workspace::{ use crate::{analyze::SqfLintData, BinaryCommand, Expression, NularCommand, UnaryCommand}; -crate::lint!(LintS18InVehicleCheck); +crate::analyze::lint!(LintS18InVehicleCheck); impl Lint for LintS18InVehicleCheck { fn ident(&self) -> &str { @@ -162,7 +162,7 @@ impl CodeS18InVehicleCheck { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); self } } diff --git a/libs/sqf/src/analyze/lints/s20_bool_static_comparison.rs b/libs/sqf/src/analyze/lints/s20_bool_static_comparison.rs index 20f7eba7..a6b223e6 100644 --- a/libs/sqf/src/analyze/lints/s20_bool_static_comparison.rs +++ b/libs/sqf/src/analyze/lints/s20_bool_static_comparison.rs @@ -5,7 +5,7 @@ use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, use crate::{analyze::SqfLintData, BinaryCommand, Expression}; -crate::lint!(LintS20BoolStaticComparison); +crate::analyze::lint!(LintS20BoolStaticComparison); impl Lint for LintS20BoolStaticComparison { fn ident(&self) -> &str { @@ -156,7 +156,7 @@ impl CodeS20BoolStaticComparison { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); self } } diff --git a/libs/sqf/src/analyze/lints/s21_invalid_comparisons.rs b/libs/sqf/src/analyze/lints/s21_invalid_comparisons.rs index 63ddf365..97cf8a95 100644 --- a/libs/sqf/src/analyze/lints/s21_invalid_comparisons.rs +++ b/libs/sqf/src/analyze/lints/s21_invalid_comparisons.rs @@ -9,7 +9,7 @@ use hemtt_workspace::{ use crate::{analyze::SqfLintData, BinaryCommand, Expression, Statement, UnaryCommand}; -crate::lint!(LintS21InvalidComparisons); +crate::analyze::lint!(LintS21InvalidComparisons); impl Lint for LintS21InvalidComparisons { fn ident(&self) -> &str { @@ -412,7 +412,7 @@ impl CodeS21InvalidComparisons { fn generate_processed(mut self, processed: &Processed) -> Self { let Some(mut diag) = - Diagnostic::new_for_processed(&self, self.issue.span_a.clone(), processed) + Diagnostic::from_code_processed(&self, self.issue.span_a.clone(), processed) else { return self; }; diff --git a/libs/sqf/src/analyze/lints/s22_this_call.rs b/libs/sqf/src/analyze/lints/s22_this_call.rs index 43d2db3b..24409a73 100644 --- a/libs/sqf/src/analyze/lints/s22_this_call.rs +++ b/libs/sqf/src/analyze/lints/s22_this_call.rs @@ -8,7 +8,7 @@ use hemtt_workspace::{ use crate::{analyze::SqfLintData, BinaryCommand, Expression}; -crate::lint!(LintS22ThisCall); +crate::analyze::lint!(LintS22ThisCall); impl Lint for LintS22ThisCall { fn ident(&self) -> &str { @@ -138,7 +138,7 @@ impl CodeS22ThisCall { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); self } } diff --git a/libs/sqf/src/analyze/lints/s23_reassign_reserved_variable.rs b/libs/sqf/src/analyze/lints/s23_reassign_reserved_variable.rs index 3b72da32..be1c7357 100644 --- a/libs/sqf/src/analyze/lints/s23_reassign_reserved_variable.rs +++ b/libs/sqf/src/analyze/lints/s23_reassign_reserved_variable.rs @@ -8,7 +8,7 @@ use hemtt_workspace::{ use crate::{analyze::SqfLintData, BinaryCommand, Expression, Statement, UnaryCommand}; -crate::lint!(LintS23ReassignReservedVariable); +crate::analyze::lint!(LintS23ReassignReservedVariable); impl Lint for LintS23ReassignReservedVariable { fn ident(&self) -> &str { @@ -233,7 +233,7 @@ impl CodeS23ReassignReservedVariable { } fn generate_processed(mut self, processed: &Processed) -> Self { - let Some(mut diag) = Diagnostic::new_for_processed(&self, self.variant.span(), processed) else { + let Some(mut diag) = Diagnostic::from_code_processed(&self, self.variant.span(), processed) else { return self }; diag = diag.clear_labels(); diff --git a/libs/sqf/src/analyze/lints/s24_marker_spam.rs b/libs/sqf/src/analyze/lints/s24_marker_spam.rs index d192bc71..7c6cf3ce 100644 --- a/libs/sqf/src/analyze/lints/s24_marker_spam.rs +++ b/libs/sqf/src/analyze/lints/s24_marker_spam.rs @@ -8,7 +8,7 @@ use hemtt_workspace::{ use crate::{analyze::SqfLintData, BinaryCommand, Expression, Statement}; -crate::lint!(LintS24MarkerSpam); +crate::analyze::lint!(LintS24MarkerSpam); impl Lint for LintS24MarkerSpam { fn ident(&self) -> &str { @@ -171,7 +171,7 @@ impl CodeS24MarkerSpam { } fn generate_processed(mut self, processed: &Processed) -> Self { - let Some(mut diag) = Diagnostic::new_for_processed(&self, self.calls.first().expect("at least one call").1.clone(), processed) else { + let Some(mut diag) = Diagnostic::from_code_processed(&self, self.calls.first().expect("at least one call").1.clone(), processed) else { return self; }; diag = diag.clear_labels(); diff --git a/libs/sqf/src/analyze/mod.rs b/libs/sqf/src/analyze/mod.rs index 7c7ae3b4..81e0c624 100644 --- a/libs/sqf/src/analyze/mod.rs +++ b/libs/sqf/src/analyze/mod.rs @@ -8,6 +8,7 @@ use hemtt_common::config::ProjectConfig; use hemtt_workspace::{ addons::Addon, lint::LintManager, + lint_manager, reporting::{Codes, Processed}, }; use lints::s02_event_handlers::{ @@ -20,46 +21,17 @@ use crate::{ UnaryCommand, }; -#[linkme::distributed_slice] -pub static SQF_LINTS: [std::sync::LazyLock< - std::sync::Arc>>, ->]; - -#[macro_export] -macro_rules! lint { - ($name:ident) => { - #[allow(clippy::module_name_repetitions)] - pub struct $name; - #[linkme::distributed_slice($crate::analyze::SQF_LINTS)] - static LINT_ADD: std::sync::LazyLock< - std::sync::Arc>>, - > = std::sync::LazyLock::new(|| std::sync::Arc::new(Box::new($name))); - }; -} - -#[must_use] -pub fn lint_check(project: &ProjectConfig, database: Arc) -> Codes { - let mut manager: LintManager = LintManager::new( - project.lints().sqf().clone(), - (Arc::new(Addon::test_addon()), database), - ); - if let Err(lint_errors) = - manager.extend(SQF_LINTS.iter().map(|l| (**l).clone()).collect::>()) - { - return lint_errors; - } - if let Err(lint_errors) = manager.push_group( +lint_manager!( + sqf, + vec![( vec![ Arc::new(Box::new(LintS02EventUnknown)), Arc::new(Box::new(LintS02EventIncorrectCommand)), Arc::new(Box::new(LintS02EventInsufficientVersion)), ], Box::new(EventHandlerRunner), - ) { - return lint_errors; - } - vec![] -} + )] +); #[must_use] pub fn analyze( @@ -71,7 +43,6 @@ pub fn analyze( ) -> Codes { let mut manager: LintManager = LintManager::new( project.map_or_else(Default::default, |project| project.lints().sqf().clone()), - (addon, database), ); if let Err(lint_errors) = manager.extend(SQF_LINTS.iter().map(|l| (**l).clone()).collect::>()) @@ -88,7 +59,7 @@ pub fn analyze( ) { return lint_errors; } - statements.analyze(project, processed, &manager) + statements.analyze(&(addon, database), project, processed, &manager) } pub type SqfLintData = (Arc, Arc); @@ -96,12 +67,13 @@ pub type SqfLintData = (Arc, Arc); pub trait Analyze: Sized + 'static { fn analyze( &self, + data: &SqfLintData, project: Option<&ProjectConfig>, processed: &Processed, manager: &LintManager, ) -> Codes { let mut codes = vec![]; - codes.extend(manager.run(project, Some(processed), self)); + codes.extend(manager.run(data, project, Some(processed), self)); codes } } @@ -113,14 +85,15 @@ impl Analyze for BinaryCommand {} impl Analyze for Statements { fn analyze( &self, + data: &SqfLintData, project: Option<&ProjectConfig>, processed: &Processed, manager: &LintManager, ) -> Codes { let mut codes = vec![]; - codes.extend(manager.run(project, Some(processed), self)); + codes.extend(manager.run(data, project, Some(processed), self)); for statement in self.content() { - codes.extend(statement.analyze(project, processed, manager)); + codes.extend(statement.analyze(data, project, processed, manager)); } codes } @@ -129,17 +102,18 @@ impl Analyze for Statements { impl Analyze for Statement { fn analyze( &self, + data: &SqfLintData, project: Option<&ProjectConfig>, processed: &Processed, manager: &LintManager, ) -> Codes { let mut codes = vec![]; - codes.extend(manager.run(project, Some(processed), self)); + codes.extend(manager.run(data, project, Some(processed), self)); match self { Self::Expression(exp, _) | Self::AssignLocal(_, exp, _) | Self::AssignGlobal(_, exp, _) => { - codes.extend(exp.analyze(project, processed, manager)); + codes.extend(exp.analyze(data, project, processed, manager)); } } codes @@ -149,30 +123,31 @@ impl Analyze for Statement { impl Analyze for Expression { fn analyze( &self, + data: &SqfLintData, project: Option<&ProjectConfig>, processed: &Processed, manager: &LintManager, ) -> Codes { let mut codes = vec![]; - codes.extend(manager.run(project, Some(processed), self)); + codes.extend(manager.run(data, project, Some(processed), self)); match self { Self::Array(exp, _) => { for e in exp { - codes.extend(e.analyze(project, processed, manager)); + codes.extend(e.analyze(data, project, processed, manager)); } } - Self::Code(s) => codes.extend(s.analyze(project, processed, manager)), + Self::Code(s) => codes.extend(s.analyze(data, project, processed, manager)), Self::NularCommand(nc, _) => { - codes.extend(nc.analyze(project, processed, manager)); + codes.extend(nc.analyze(data, project, processed, manager)); } Self::UnaryCommand(uc, exp, _) => { - codes.extend(uc.analyze(project, processed, manager)); - codes.extend(exp.analyze(project, processed, manager)); + codes.extend(uc.analyze(data, project, processed, manager)); + codes.extend(exp.analyze(data, project, processed, manager)); } Self::BinaryCommand(bc, exp_left, exp_right, _) => { - codes.extend(bc.analyze(project, processed, manager)); - codes.extend(exp_left.analyze(project, processed, manager)); - codes.extend(exp_right.analyze(project, processed, manager)); + codes.extend(bc.analyze(data, project, processed, manager)); + codes.extend(exp_left.analyze(data, project, processed, manager)); + codes.extend(exp_right.analyze(data, project, processed, manager)); } _ => {} } diff --git a/libs/sqf/src/parser/codes/spe1_invalid_token.rs b/libs/sqf/src/parser/codes/spe1_invalid_token.rs index 02e2d60e..f42dab5a 100644 --- a/libs/sqf/src/parser/codes/spe1_invalid_token.rs +++ b/libs/sqf/src/parser/codes/spe1_invalid_token.rs @@ -32,7 +32,7 @@ impl InvalidToken { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); self } } diff --git a/libs/sqf/src/parser/codes/spe2_unparseable.rs b/libs/sqf/src/parser/codes/spe2_unparseable.rs index 8ba23bb5..01b5639c 100644 --- a/libs/sqf/src/parser/codes/spe2_unparseable.rs +++ b/libs/sqf/src/parser/codes/spe2_unparseable.rs @@ -32,7 +32,7 @@ impl UnparseableSyntax { } fn generate_processed(mut self, processed: &Processed) -> Self { - self.diagnostic = Diagnostic::new_for_processed(&self, self.span.clone(), processed); + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); self } } diff --git a/libs/stringtable/Cargo.toml b/libs/stringtable/Cargo.toml index a9348628..91f81295 100644 --- a/libs/stringtable/Cargo.toml +++ b/libs/stringtable/Cargo.toml @@ -9,10 +9,15 @@ license = "GPL-2.0" workspace = true [dependencies] -indexmap = { workspace = true } +hemtt-common = { path = "../common", version = "1.0.0" } +hemtt-workspace = { path = "../workspace", version = "1.0.0" } + +automod = { workspace = true } +linkme = { workspace = true } paste = { workspace = true } quick-xml = { version = "0.36.2", features = ["serialize"] } serde = { workspace = true, features = ["derive"] } +tracing = { workspace = true } [dev-dependencies] insta = { workspace = true } diff --git a/libs/stringtable/src/analyze/lints/01_sorted.rs b/libs/stringtable/src/analyze/lints/01_sorted.rs new file mode 100644 index 00000000..7b0fbf41 --- /dev/null +++ b/libs/stringtable/src/analyze/lints/01_sorted.rs @@ -0,0 +1,181 @@ +use std::{io::BufReader, sync::Arc}; + +use hemtt_common::config::LintConfig; +use hemtt_workspace::{addons::Addon, lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, Codes, Diagnostic, Severity}}; +use tracing::debug; + +use crate::{analyze::SqfLintData, Project}; + +crate::analyze::lint!(LintL01Sorted); + +impl Lint for LintL01Sorted { + fn ident(&self) -> &str { + "sorted" + } + + fn sort(&self) -> u32 { + 10 + } + + fn description(&self) -> &str { + "Checks if stringtables are sorted" + } + + fn documentation(&self) -> &str { + "Stringtables should be sorted alphabetically and the keys in the order from the [Arma 3 Wiki](https://community.bistudio.com/wiki/Stringtable.xml#Supported_Languages)." + } + + fn default_config(&self) -> LintConfig { + LintConfig::warning() + } + + fn runners(&self) -> Vec>> { + vec![Box::new(Runner)] + } +} + +pub struct Runner; +impl LintRunner for Runner { + type Target = Vec; + fn run( + &self, + _project: Option<&hemtt_common::config::ProjectConfig>, + config: &hemtt_common::config::LintConfig, + _processed: Option<&hemtt_workspace::reporting::Processed>, + target: &Vec, + data: &SqfLintData, + ) -> Codes { + let mut unsorted = Vec::new(); + let mut codes: Codes = Vec::new(); + for addon in target { + let stringtable_path = data.workspace() + .join(addon.folder()).expect("vfs issue") + .join("stringtable.xml").expect("vfs issue"); + if stringtable_path.exists().expect("vfs issue") { + let existing = stringtable_path.read_to_string().expect("vfs issue"); + match Project::from_reader(BufReader::new(existing.as_bytes())) { + Ok(mut project) => { + project.sort(); + let mut writer = String::new(); + if let Err(e) = project.to_writer(&mut writer) { + panic!("Failed to write stringtable for {}: {}", addon.folder(), e); + } + if writer != existing { + unsorted.push(addon.folder().to_string()); + } + } + Err(e) => { + debug!("Failed to parse stringtable for {}: {}", addon.folder(), e); + codes.push(Arc::new(CodeStringtableInvalid::new(addon.folder()))); + } + } + } + } + if unsorted.len() <= 3 { + for addon in unsorted { + codes.push(Arc::new(CodeStringtableNotSorted::new( + Unsorted::Addon(addon), + config.severity(), + ))); + } + } else { + codes.push(Arc::new(CodeStringtableNotSorted::new( + Unsorted::Addons(unsorted), + config.severity(), + ))); + } + codes + } +} + +pub enum Unsorted { + Addon(String), + Addons(Vec), +} + +#[allow(clippy::module_name_repetitions)] +pub struct CodeStringtableNotSorted { + unsorted: Unsorted, + severity: Severity, + diagnostic: Option, +} + +impl Code for CodeStringtableNotSorted { + fn ident(&self) -> &'static str { + "L-L01" + } + + fn severity(&self) -> Severity { + self.severity + } + + fn message(&self) -> String { + match &self.unsorted { + Unsorted::Addon(addon) => format!("Stringtable in `{addon}` is not sorted"), + Unsorted::Addons(addons) => { + format!("Stringtables in {} addons are not sorted", addons.len()) + } + } + } + + fn help(&self) -> Option { + Some("Run `hemtt ln sort` to sort the stringtable".to_string()) + } + + fn diagnostic(&self) -> Option { + self.diagnostic.clone() + } +} + +impl CodeStringtableNotSorted { + #[must_use] + pub fn new(unsorted: Unsorted, severity: Severity) -> Self { + Self { + unsorted, + severity, + diagnostic: None, + } + .generate_processed() + } + + fn generate_processed(mut self) -> Self { + self.diagnostic = Some(Diagnostic::from_code(&self)); + self + } +} + +#[allow(clippy::module_name_repetitions)] +pub struct CodeStringtableInvalid { + addon: String, + diagnostic: Option, +} + +impl Code for CodeStringtableInvalid { + fn ident(&self) -> &'static str { + "L-L02" + } + + fn message(&self) -> String { + format!("Stringtable in `{}` is invalid", self.addon) + } + + fn diagnostic(&self) -> Option { + self.diagnostic.clone() + } +} + +impl CodeStringtableInvalid { + #[must_use] + pub fn new(addon: String) -> Self { + Self { + addon, + diagnostic: None, + } + .generate_processed() + } + + fn generate_processed(mut self) -> Self { + self.diagnostic = Some(Diagnostic::from_code(&self)); + self + } +} diff --git a/libs/stringtable/src/analyze/mod.rs b/libs/stringtable/src/analyze/mod.rs new file mode 100644 index 00000000..e628df28 --- /dev/null +++ b/libs/stringtable/src/analyze/mod.rs @@ -0,0 +1,60 @@ +use hemtt_common::config::ProjectConfig; +use hemtt_workspace::{ + addons::Addon, lint::LintManager, lint_manager, reporting::Codes, WorkspacePath, +}; + +pub mod lints { + automod::dir!(pub "src/analyze/lints"); +} + +lint_manager!(stringtable, vec![]); + +pub struct SqfLintData { + workspace: WorkspacePath, +} + +impl SqfLintData { + #[must_use] + pub const fn workspace(&self) -> &WorkspacePath { + &self.workspace + } +} + +pub fn lint_addon( + workspace: WorkspacePath, + addon: &Addon, + project: Option<&ProjectConfig>, +) -> Codes { + let mut manager = LintManager::new(project.map_or_else(Default::default, |project| { + project.lints().stringtables().clone() + })); + if let Err(e) = manager.extend( + STRINGTABLE_LINTS + .iter() + .map(|l| (**l).clone()) + .collect::>(), + ) { + return e; + } + manager.run(&SqfLintData { workspace }, project, None, addon) +} + +#[allow(clippy::ptr_arg)] // Needed for &Vec for &dyn Any +pub fn lint_addons( + workspace: WorkspacePath, + addons: &Vec, + project: Option<&ProjectConfig>, +) -> Codes { + let mut manager = LintManager::new(project.map_or_else(Default::default, |project| { + project.lints().stringtables().clone() + })); + if let Err(e) = manager.extend( + STRINGTABLE_LINTS + .iter() + .map(|l| (**l).clone()) + .collect::>(), + ) { + return e; + } + manager.run(&SqfLintData { workspace }, project, None, addons) +} diff --git a/libs/stringtable/src/lib.rs b/libs/stringtable/src/lib.rs index fd6c02fa..c404ea2a 100644 --- a/libs/stringtable/src/lib.rs +++ b/libs/stringtable/src/lib.rs @@ -1,6 +1,7 @@ use quick_xml::se::Serializer; use serde::{Deserialize, Serialize}; +pub mod analyze; mod key; mod package; mod totals; diff --git a/libs/workspace/Cargo.toml b/libs/workspace/Cargo.toml index 1ff392bf..db0af955 100644 --- a/libs/workspace/Cargo.toml +++ b/libs/workspace/Cargo.toml @@ -15,11 +15,13 @@ hemtt-pbo = { path = "../pbo", version = "1.0.0" } ansi_term = "0.12.1" codespan-reporting = { workspace = true } dirs = { workspace = true } +linkme = { workspace = true } +paste = { workspace = true } serde = { workspace = true } +supports-hyperlinks = { workspace = true } terminal-link = { workspace = true } toml = { workspace = true } tracing = { workspace = true } -supports-hyperlinks = { workspace = true } vfs = { workspace = true } tower-lsp = { workspace = true, optional = true } diff --git a/libs/workspace/src/lib.rs b/libs/workspace/src/lib.rs index c1f0c79b..ee102f06 100644 --- a/libs/workspace/src/lib.rs +++ b/libs/workspace/src/lib.rs @@ -10,6 +10,10 @@ use pdrive::PDrive; use tracing::trace; use vfs::{AltrootFS, MemoryFS, OverlayFS, PhysicalFS, VfsPath}; +// Re-export for macros +pub use linkme; +pub use paste; + pub mod addons; pub mod error; pub mod lint; diff --git a/libs/workspace/src/lint/macros.rs b/libs/workspace/src/lint/macros.rs new file mode 100644 index 00000000..ad91cc7d --- /dev/null +++ b/libs/workspace/src/lint/macros.rs @@ -0,0 +1,47 @@ +#[macro_export] +macro_rules! lint_manager { + ($ident:ident, $groups:expr) => { + $crate::paste::paste! { + #[linkme::distributed_slice] + pub static [<$ident:upper _LINTS>]: [std::sync::LazyLock< + std::sync::Arc>>, + >]; + + #[allow(unused_macros)] + macro_rules! lint { + ($name:ident) => { + #[allow(clippy::module_name_repetitions)] + pub struct $name; + #[linkme::distributed_slice(super::super::[<$ident:upper _LINTS>])] + static LINT_ADD: std::sync::LazyLock< + std::sync::Arc>>, + > = std::sync::LazyLock::new(|| std::sync::Arc::new(Box::new($name))); + }; + } + pub(crate) use lint; + + #[must_use] + pub fn lint_check( + config: std::collections::HashMap, + ) -> $crate::reporting::Codes { + let mut manager: $crate::lint::LintManager = + $crate::lint::LintManager::new(config); + if let Err(lint_errors) = + manager.extend([<$ident:upper _LINTS>].iter().map(|l| (**l).clone()).collect::>()) + { + return lint_errors; + } + let groups: Vec<( + $crate::lint::Lints, + Box>, + )> = $groups; + for group in groups { + if let Err(lint_errors) = manager.push_group(group.0, group.1) { + return lint_errors; + } + } + vec![] + } + } + }; +} diff --git a/libs/workspace/src/lint.rs b/libs/workspace/src/lint/mod.rs similarity index 96% rename from libs/workspace/src/lint.rs rename to libs/workspace/src/lint/mod.rs index b3fb0a3a..0ef1e1ee 100644 --- a/libs/workspace/src/lint.rs +++ b/libs/workspace/src/lint/mod.rs @@ -1,3 +1,5 @@ +pub mod macros; + use std::{collections::HashMap, sync::Arc}; use codespan_reporting::diagnostic::Severity; @@ -117,17 +119,15 @@ pub struct LintManager { lints: Lints, groups: Vec<(Lints, Box>)>, configs: HashMap, - data: D, } impl LintManager { #[must_use] - pub fn new(configs: HashMap, data: D) -> Self { + pub fn new(configs: HashMap) -> Self { Self { lints: vec![], groups: vec![], configs, - data, } } @@ -205,6 +205,7 @@ impl LintManager { pub fn run( &self, + data: &D, project: Option<&ProjectConfig>, processed: Option<&Processed>, target: &dyn std::any::Any, @@ -222,7 +223,7 @@ impl LintManager { } lint.runners() .iter() - .flat_map(|runner| runner.run(project, &config, processed, target, &self.data)) + .flat_map(|runner| runner.run(project, &config, processed, target, data)) .collect::() }) .chain(self.groups.iter().flat_map(|(lints, runner)| { @@ -240,7 +241,7 @@ impl LintManager { if configs.is_empty() { return vec![]; } - runner.run(project, configs, processed, target, &self.data) + runner.run(project, configs, processed, target, data) })) .collect() } @@ -401,22 +402,21 @@ mod tests { lints: vec![Arc::new(Box::new(LintA)), Arc::new(Box::new(LintB))], groups: vec![], configs: HashMap::new(), - data: (), }; let target_a = TypeA; let target_b = TypeB; let target_c = TypeC; - let codes = manager.run(None, None, &target_a); + let codes = manager.run(&(), None, None, &target_a); assert_eq!(codes.len(), 1); assert_eq!(codes[0].ident(), "CodeA"); - let codes = manager.run(None, None, &target_b); + let codes = manager.run(&(), None, None, &target_b); assert_eq!(codes.len(), 1); assert_eq!(codes[0].ident(), "CodeB"); - let codes = manager.run(None, None, &target_c); + let codes = manager.run(&(), None, None, &target_c); assert_eq!(codes.len(), 0); } } diff --git a/libs/workspace/src/path.rs b/libs/workspace/src/path.rs index 79e4979b..06712e71 100644 --- a/libs/workspace/src/path.rs +++ b/libs/workspace/src/path.rs @@ -1,4 +1,8 @@ -use std::{hash::Hasher, sync::Arc}; +use std::{ + hash::Hasher, + path::{Path, PathBuf}, + sync::Arc, +}; use hemtt_common::strip::StripInsensitive; use vfs::{SeekAndWrite, VfsPath}; @@ -184,6 +188,7 @@ impl WorkspacePath { }) } + #[allow(clippy::too_many_lines)] /// Locate a path in the workspace /// /// Checks in order: @@ -194,6 +199,9 @@ impl WorkspacePath { /// /// # Errors /// [`Error::Vfs`] if the path could not be located + /// + /// # Panics + /// If the found path is not valid utf-8 pub fn locate(&self, path: &str) -> Result, Error> { fn is_wrong_case(on_disk: &VfsPath, requested: &str) -> bool { let on_disk = on_disk.as_str().replace('\\', "/"); @@ -244,25 +252,13 @@ impl WorkspacePath { .iter() .find(|(p, _)| path_lower.starts_with(&format!("{}/", p.to_lowercase()))) { - // Windows needs case insensitivity because p3ds are a - // disaster. On Linux we'll be more strict to avoid - // pain and suffering. - let ret_path = if cfg!(windows) { - root.join( - path_lower - .strip_prefix(&base.to_lowercase()) - .unwrap_or(&path) - .strip_prefix('/') - .unwrap_or(&path), - )? - } else { - root.join( - path.strip_prefix_insensitive(base) - .unwrap_or(&path) - .strip_prefix('/') - .unwrap_or(&path), - )? - }; + let ret_path = root.join( + path.strip_prefix_insensitive(base) + .unwrap_or(&path) + .strip_prefix('/') + .unwrap_or(&path), + )?; + let path_str = ret_path.as_str().trim_start_matches('/'); if ret_path.exists()? { return Ok(Some(LocateResult { case_mismatch: if is_wrong_case(&ret_path, &path) { @@ -277,7 +273,35 @@ impl WorkspacePath { }), }, })); + } else if let Some(insensitive) = exists_case_insensitive(Path::new(path_str)) { + return Ok(Some(LocateResult { + case_mismatch: Some(insensitive.to_string_lossy().to_string()), + path: Self { + data: Arc::new(WorkspacePathData { + path: self + .data + .workspace + .vfs + .join(insensitive.to_str().expect("utf-8"))?, + workspace: self.data.workspace.clone(), + }), + }, + })); } + } else if let Some(insensitive) = exists_case_insensitive(Path::new(&path)) { + return Ok(Some(LocateResult { + case_mismatch: Some(insensitive.to_string_lossy().to_string()), + path: Self { + data: Arc::new(WorkspacePathData { + path: self + .data + .workspace + .vfs + .join(insensitive.to_str().expect("utf-8"))?, + workspace: self.data.workspace.clone(), + }), + }, + })); } } let ret_path = self.data.path.parent().join(&path)?; @@ -393,3 +417,36 @@ pub struct LocateResult { pub path: WorkspacePath, pub case_mismatch: Option, } + +/// Check if a path exists in a case-insensitive manner. +fn exists_case_insensitive(path: &Path) -> Option { + exists_case_insensitive_from(path, PathBuf::from(".")) + .or_else(|| exists_case_insensitive_from(path, PathBuf::from("./include"))) +} + +fn exists_case_insensitive_from(path: &Path, from: PathBuf) -> Option { + let mut current_path = from; + for component in path.components() { + if let std::path::Component::Normal(name) = component { + let mut found = false; + if let Ok(entries) = std::fs::read_dir(¤t_path) { + for entry in entries.filter_map(Result::ok) { + if entry.file_name().eq_ignore_ascii_case(name) { + current_path.push(entry.file_name()); + found = true; + break; + } + } + } + if !found { + return None; + } + } + } + + if current_path.exists() { + Some(current_path) + } else { + None + } +} diff --git a/libs/workspace/src/reporting/diagnostic/mod.rs b/libs/workspace/src/reporting/diagnostic/mod.rs index d1900ade..7db04677 100644 --- a/libs/workspace/src/reporting/diagnostic/mod.rs +++ b/libs/workspace/src/reporting/diagnostic/mod.rs @@ -43,7 +43,22 @@ impl Diagnostic { } } - pub fn new_for_processed( + pub fn from_code(code: &impl Code) -> Self { + let mut diag = Self::new(code.ident(), code.message()).set_severity(code.severity()); + diag.link = code.link().map(std::string::ToString::to_string); + if let Some(note) = code.note() { + diag.notes.push(note); + } + if let Some(help) = code.help() { + diag.help.push(help); + } + if let Some(suggestion) = code.suggestion() { + diag.suggestions.push(suggestion); + } + diag + } + + pub fn from_code_processed( code: &impl Code, mut span: std::ops::Range, processed: &crate::reporting::Processed,