diff --git a/Cargo.lock b/Cargo.lock index 8691c8660..c55aa7276 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1610,11 +1610,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 +1630,8 @@ dependencies = [ "dirs", "hemtt-common", "hemtt-pbo", + "linkme", + "paste", "serde", "supports-hyperlinks", "terminal-link", diff --git a/bin/src/commands/build.rs b/bin/src/commands/build.rs index 5a08eccda..61d3eb9c7 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 8d7913e9c..d091fa20a 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 35fcc5f01..7a9837cba 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 8e6581afe..d37349139 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/modules/mod.rs b/bin/src/modules/mod.rs index 2c1b32e7c..bb629f698 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/rapifier.rs b/bin/src/modules/rapifier.rs index 9b17f6816..5ffe06d45 100644 --- a/bin/src/modules/rapifier.rs +++ b/bin/src/modules/rapifier.rs @@ -3,7 +3,7 @@ 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}; @@ -25,7 +25,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) } diff --git a/bin/src/modules/sqf.rs b/bin/src/modules/sqf.rs index cb5f69214..3aabd22e2 100644 --- a/bin/src/modules/sqf.rs +++ b/bin/src/modules/sqf.rs @@ -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) } diff --git a/bin/src/modules/stringtables/mod.rs b/bin/src/modules/stringtables/mod.rs new file mode 100644 index 000000000..0cd524aac --- /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/report.rs b/bin/src/report.rs index 18185c5a7..e371e6e0a 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 f3ccfa3dc..a3a05c986 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 0bafe81e0..42b34f64e 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 98e5333ad..ea5df1935 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 495256e75..333d77395 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 916abee98..9935c45c2 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 35d831574..b79223e40 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 3763799e4..212f48418 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 673d7145d..cc79079a4 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 c8a63778f..19d9bed57 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 d82512dcd..20a08df87 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 6e1a7abfb..c73dc5a72 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 4e49fddc0..152acf6b7 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 098e789a9..37a630ca1 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 87c0d4dee..56d55eab9 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 47414d860..80fe7832a 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 2d6ddaa55..48d33d2ba 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/sqf/src/analyze/lints/s01_command_required_version.rs b/libs/sqf/src/analyze/lints/s01_command_required_version.rs index 9dca5d896..ef09a37b1 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 2048a538f..9fe70894d 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 1a996b532..695986d4d 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 90d33d00a..d0ad6faaf 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 75768fe7f..698ff2048 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 0dd1f67ab..a14f0539c 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 d6c393989..ac24c2757 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 ae908adba..d95e961fd 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 18f2e0519..26dfda1c5 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 4dbd95e9d..edc00e881 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 3f75a5b44..f960e9fb3 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 9a25bcd0b..d2f30d116 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 20f7eba7a..a6b223e62 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 63ddf365a..97cf8a95f 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 43d2db3b4..24409a733 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 3b72da32d..be1c73579 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 d192bc712..7c6cf3cec 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 7c7ae3b4a..81e0c6245 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 02e2d60ec..f42dab5a9 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 8ba23bb5d..01b5639ce 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 a93486287..91f812957 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 000000000..7b0fbf41a --- /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 000000000..e628df281 --- /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 fd6c02fa2..c404ea2ac 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 1ff392bf9..db0af955d 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 c1f0c79b9..ee102f06c 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 000000000..ad91cc7d8 --- /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 b3fb0a3ac..0ef1e1ee2 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/reporting/diagnostic/mod.rs b/libs/workspace/src/reporting/diagnostic/mod.rs index d1900ade4..7db046770 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,