diff --git a/Cargo.lock b/Cargo.lock index f208f204..d670d854 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1568,6 +1568,7 @@ dependencies = [ "linkme", "lsp-types 0.97.0", "paste", + "toml 0.8.19", "vfs", ] diff --git a/book-gen/src/lints.rs b/book-gen/src/lints.rs index 95d313ce..a7ef1340 100644 --- a/book-gen/src/lints.rs +++ b/book-gen/src/lints.rs @@ -5,7 +5,7 @@ use hemtt_sqf::analyze::{ lints::s02_event_handlers::{ LintS02EventIncorrectCommand, LintS02EventInsufficientVersion, LintS02EventUnknown, }, - SqfLintData, SQF_LINTS, + LintData, SQF_LINTS, }; use hemtt_workspace::lint::{Lint, Lints}; use mdbook::book::Chapter; @@ -43,7 +43,7 @@ fn sqf(chapter: &mut Chapter) { .iter() .map(|l| (**l).clone()) .chain({ - let lints: Lints = vec![ + let lints: Lints = vec![ Arc::new(Box::new(LintS02EventUnknown)), Arc::new(Box::new(LintS02EventIncorrectCommand)), Arc::new(Box::new(LintS02EventInsufficientVersion)), diff --git a/libs/config/Cargo.toml b/libs/config/Cargo.toml index 2fddc28e..a993dd4c 100644 --- a/libs/config/Cargo.toml +++ b/libs/config/Cargo.toml @@ -21,6 +21,7 @@ byteorder = { workspace = true } chumsky = { workspace = true } linkme = { workspace = true } lsp-types = { workspace = true } +toml = { workspace = true } vfs = { workspace = true } [dev-dependencies] diff --git a/libs/config/src/analyze/lints/c01_invalid_value.rs b/libs/config/src/analyze/lints/c01_invalid_value.rs index 718fa318..229b0ec3 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::{analyze::SqfLintData, Item, Value}; +use crate::{analyze::LintData, Item, Value}; crate::analyze::lint!(LintC01InvalidValue); -impl Lint for LintC01InvalidValue { +impl Lint for LintC01InvalidValue { fn ident(&self) -> &'static 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: &SqfLintData, + _data: &LintData, ) -> 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: &SqfLintData, + _data: &LintData, ) -> Codes { let Some(processed) = processed else { return vec![]; diff --git a/libs/config/src/analyze/lints/c02_duplicate_property.rs b/libs/config/src/analyze/lints/c02_duplicate_property.rs index dc182134..081d3264 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::{analyze::SqfLintData, Class, Config, Ident, Property}; +use crate::{analyze::LintData, Class, Config, Ident, Property}; crate::analyze::lint!(LintC02DuplicateProperty); -impl Lint for LintC02DuplicateProperty { +impl Lint for LintC02DuplicateProperty { fn ident(&self) -> &'static 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: &SqfLintData, + _data: &LintData, ) -> Codes { let Some(processed) = processed else { return vec![]; diff --git a/libs/config/src/analyze/lints/c03_duplicate_classes.rs b/libs/config/src/analyze/lints/c03_duplicate_classes.rs index 825c3d12..7deb084f 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::{analyze::SqfLintData, Class, Config, Property}; +use crate::{analyze::LintData, Class, Config, Property}; crate::analyze::lint!(LintC03DuplicateClasses); -impl Lint for LintC03DuplicateClasses { +impl Lint for LintC03DuplicateClasses { fn ident(&self) -> &'static 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: &SqfLintData, + _data: &LintData, ) -> Codes { let Some(processed) = processed else { return vec![]; diff --git a/libs/config/src/analyze/lints/c04_external_missing.rs b/libs/config/src/analyze/lints/c04_external_missing.rs index bbf20e6a..3ddb0eec 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::{analyze::SqfLintData, Class, Config, Property}; +use crate::{analyze::LintData, Class, Config, Property}; crate::analyze::lint!(LintC04ExternalMissing); -impl Lint for LintC04ExternalMissing { +impl Lint for LintC04ExternalMissing { fn ident(&self) -> &'static 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: &SqfLintData, + _data: &LintData, ) -> Vec> { let Some(processed) = processed else { return vec![]; 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 19fb5fc0..64e07428 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::{analyze::SqfLintData, Class, Property}; +use crate::{analyze::LintData, Class, Property}; crate::analyze::lint!(LintC05ExternalParentCase); -impl Lint for LintC05ExternalParentCase { +impl Lint for LintC05ExternalParentCase { fn ident(&self) -> &'static 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: &SqfLintData, + _data: &LintData, ) -> Vec> { let Some(processed) = processed else { return vec![]; diff --git a/libs/config/src/analyze/lints/c06_unexpected_array.rs b/libs/config/src/analyze/lints/c06_unexpected_array.rs index 5f46cb2a..37a5737d 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::{analyze::SqfLintData, Property, Value}; +use crate::{analyze::LintData, Property, Value}; crate::analyze::lint!(LintC06UnexpectedArray); -impl Lint for LintC06UnexpectedArray { +impl Lint for LintC06UnexpectedArray { fn ident(&self) -> &'static 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: &SqfLintData, + _data: &LintData, ) -> Vec> { let Some(processed) = processed else { return vec![]; diff --git a/libs/config/src/analyze/lints/c07_expected_array.rs b/libs/config/src/analyze/lints/c07_expected_array.rs index ebdf5bfc..d5e76ce0 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::{analyze::SqfLintData, Property, Value}; +use crate::{analyze::LintData, Property, Value}; crate::analyze::lint!(LintC07ExpectedArray); -impl Lint for LintC07ExpectedArray { +impl Lint for LintC07ExpectedArray { fn ident(&self) -> &'static 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: &SqfLintData, + _data: &LintData, ) -> Vec> { let Some(processed) = processed else { return vec![]; diff --git a/libs/config/src/analyze/lints/c08_missing_semicolon.rs b/libs/config/src/analyze/lints/c08_missing_semicolon.rs index 005bdabc..be119bf7 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::{analyze::SqfLintData, Property}; +use crate::{analyze::LintData, Property}; crate::analyze::lint!(LintC08MissingSemicolon); -impl Lint for LintC08MissingSemicolon { +impl Lint for LintC08MissingSemicolon { fn ident(&self) -> &'static 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: &SqfLintData, + _data: &LintData, ) -> Vec> { let Some(processed) = processed else { return vec![]; 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 ebd60284..27910ced 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::{analyze::SqfLintData, Class, Config, Ident, Item, Property, Str, Value}; +use crate::{analyze::LintData, Class, Config, Ident, Item, Property, Str, Value}; crate::analyze::lint!(LintC09MagwellMissingMagazine); -impl Lint for LintC09MagwellMissingMagazine { +impl Lint for LintC09MagwellMissingMagazine { fn ident(&self) -> &'static 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: &SqfLintData, + _data: &LintData, ) -> Codes { let Some(processed) = processed else { return vec![]; 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 8e9db632..35eb4d8a 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::{analyze::SqfLintData, Class, Property}; +use crate::{analyze::LintData, Class, Property}; crate::analyze::lint!(LintC10ClassMissingBraces); -impl Lint for LintC10ClassMissingBraces { +impl Lint for LintC10ClassMissingBraces { fn ident(&self) -> &'static 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: &SqfLintData, + _data: &LintData, ) -> Vec> { let Some(processed) = processed else { return vec![]; diff --git a/libs/config/src/analyze/lints/c11_file_type.rs b/libs/config/src/analyze/lints/c11_file_type.rs new file mode 100644 index 00000000..6cc1e621 --- /dev/null +++ b/libs/config/src/analyze/lints/c11_file_type.rs @@ -0,0 +1,299 @@ +use std::{ops::Range, sync::Arc}; + +use hemtt_common::config::{LintConfig, ProjectConfig}; +use hemtt_workspace::{ + lint::{AnyLintRunner, Lint, LintRunner}, + reporting::{Code, Diagnostic, Processed, Severity}, +}; + +use crate::{analyze::LintData, Item, Property, Str, Value}; + +crate::analyze::lint!(LintC11FileType); + +impl Lint for LintC11FileType { + fn ident(&self) -> &'static str { + "file_type" + } + + fn sort(&self) -> u32 { + 110 + } + + fn description(&self) -> &'static str { + "Reports on properties that have an unusual or missing file type" + } + + fn documentation(&self) -> &'static str { +r#"### Configuration + +- **allow_no_extension**: Allow properties to not have a file extension, default is `true`. + +```toml +[lints.config.file_type] +options.allow_no_extension = false +``` + +### Example + +**Incorrect** +```hpp +class MyClass { + model = "model.blend"; +}; +``` + +**Correct** +```hpp +class MyClass { + model = "model.p3d"; +}; +``` + +**Incorrect** +```hpp +class MyClass { + editorPreview = "preview.jgp"; +} +``` + +**Correct** +```hpp +class MyClass { + editorPreview = "preview.jpg"; +}; +``` + +**Incorrect, when `allow_no_extension` is `false`** +```hpp +class MyClass { + model = "my_model"; +}; +``` + +**Correct** +```hpp +class MyClass { + model = "my_model.p3d"; +}; +``` + +### Explanation + +Some properties require a specific file type. This lint will report on properties that have an unusual file type, from typos or incorrect file extensions. +"# + } + + fn default_config(&self) -> LintConfig { + LintConfig::warning() + } + + fn runners(&self) -> Vec>> { + vec![Box::new(Runner)] + } +} + +struct Runner; + +impl LintRunner for Runner { + type Target = crate::Property; + fn run( + &self, + _project: Option<&ProjectConfig>, + config: &LintConfig, + processed: Option<&Processed>, + target: &crate::Property, + _data: &LintData, + ) -> Vec> { + let mut codes = Vec::new(); + let Some(processed) = processed else { + return vec![]; + }; + let Property::Entry { name, value, .. } = target else { + return vec![]; + }; + let allow_no_extension = if let Some(toml::Value::Boolean(allow_no_extension)) = config.option("allow_no_extension") { + *allow_no_extension + } else { + true + }; + // Arrays + if let Value::Array(values) = value { + for value in &values.items { + if let Item::Str(value) = value { + if let Some(code) = check(name.as_str(), value, allow_no_extension, processed, config) { + codes.push(code); + } + } + } + } + let Value::Str(value) = value else { + return codes; + }; + if let Some(code) = check(name.as_str(), value, allow_no_extension, processed, config) { + codes.push(code); + } + codes + } +} + +fn check(name: &str, value: &Str, allow_no_extension: bool, processed: &Processed, config: &LintConfig) -> Option> { + let value_str = value.value(); + if name == "sound" && value_str.starts_with("db") { + return None; + } + let allowed = allowed_ext(name); + if !allowed.is_empty() { + if value_str.is_empty() { + return None; + } + let ext = if value_str.contains('.') { + value_str.split('.').last().unwrap_or("") + } else { + "" + }; + if ext.is_empty() { + if !allow_no_extension { + let span = value.span().start + 1..value.span().end - 1; + return Some(Arc::new(CodeC11MissingExtension::new(span, processed, config.severity()))); + } + return None; + } + if !allowed.contains(&ext){ + let span = value.span().start + 2 + (value_str.len() - ext.len())..value.span().end - 1; + return Some(Arc::new(CodeC11UnusualExtension::new(span, (*allowed.first().expect("not empty extensions")).to_string(), processed, config.severity()))); + } + } + None +} + +fn allowed_ext(name: &str) -> Vec<&str> { + let name = name.to_lowercase(); + if name.starts_with("animation") { + if name.starts_with("animationsource") || name == "animationlist" { + return vec![]; + } + return vec!["rtm"]; + } + if name.starts_with("dammage") { + return vec!["paa"]; + } + if name.ends_with("opticsmodel") { + return vec!["p3d"]; + } + if name.contains("sound") && !name.contains("soundset") { + return vec!["wss", "ogg", "wav"]; + } + if name.starts_with("scud") { + return vec!["rtm"]; + } + match name.as_str() { + "model" | "uimodel" | "modelspecial" | "modeloptics" | "modelmagazine" | "cartridge" => vec!["p3d"], + "editorpreview" => vec!["jpg", "jpeg", "paa", "pac"], + "uipicture" | "icon" | "picture" | "wounds" => vec!["paa", "pac"], + _ => vec![], + } +} + +pub struct CodeC11MissingExtension { + span: Range, + diagnostic: Option, + severity: Severity, +} + +impl Code for CodeC11MissingExtension { + fn ident(&self) -> &'static str { + "L-C11ME" + } + + fn link(&self) -> Option<&str> { + Some("/analysis/config.html#file_type") + } + + fn message(&self) -> String { + "a property that references a file is missing a file extension".to_string() + } + + fn label_message(&self) -> String { + "missing file extension".to_string() + } + + fn severity(&self) -> Severity { + self.severity + } + + fn diagnostic(&self) -> Option { + self.diagnostic.clone() + } +} + +impl CodeC11MissingExtension { + #[must_use] + pub fn new(span: Range, processed: &Processed, severity: Severity) -> Self { + Self { + span, + severity, + diagnostic: None, + } + .generate_processed(processed) + } + + fn generate_processed(mut self, processed: &Processed) -> Self { + self.diagnostic = Diagnostic::from_code_processed(&self, self.span.clone(), processed); + self + } +} + +pub struct CodeC11UnusualExtension { + span: Range, + expected: String, + diagnostic: Option, + severity: Severity, +} + +impl Code for CodeC11UnusualExtension { + fn ident(&self) -> &'static str { + "L-C11UE" + } + + fn link(&self) -> Option<&str> { + Some("/analysis/config.html#file_type") + } + + fn message(&self) -> String { + "a property that references a file has an unusual file type".to_string() + } + + fn note(&self) -> Option { + Some(format!("expected file type {}", self.expected)) + } + + fn label_message(&self) -> String { + "unusual file type".to_string() + } + + fn severity(&self) -> Severity { + self.severity + } + + fn diagnostic(&self) -> Option { + self.diagnostic.clone() + } +} + +impl CodeC11UnusualExtension { + #[must_use] + pub fn new(span: Range, expected: String, processed: &Processed, severity: Severity) -> Self { + Self { + span, + expected, + severity, + diagnostic: None, + } + .generate_processed(processed) + } + + fn generate_processed(mut self, processed: &Processed) -> Self { + self.diagnostic = Diagnostic::from_code_processed_skip_macros(&self, self.span.clone(), processed); + self + } +} diff --git a/libs/config/src/analyze/mod.rs b/libs/config/src/analyze/mod.rs index 80fe7832..5fdc84e5 100644 --- a/libs/config/src/analyze/mod.rs +++ b/libs/config/src/analyze/mod.rs @@ -12,7 +12,9 @@ pub mod lints { automod::dir!(pub "src/analyze/lints"); } -pub struct SqfLintData {} +pub struct LintData { + pub(crate) path: String, +} lint_manager!(config, vec![]); @@ -25,10 +27,10 @@ use crate::{Array, Class, Config, Expression, Item, Number, Property, Str, Value pub trait Analyze: Sized + 'static { fn analyze( &self, - data: &SqfLintData, + data: &LintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; codes.extend(manager.run(data, project, Some(processed), self)); @@ -43,18 +45,21 @@ impl Analyze for Expression {} impl Analyze for Config { fn analyze( &self, - data: &SqfLintData, + _data: &LintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager, + manager: &LintManager, ) -> Codes { + let data = LintData { + path: String::new(), + }; let mut codes = vec![]; - codes.extend(manager.run(data, project, Some(processed), self)); - codes.extend(manager.run(data, 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(data, project, processed, manager)), + .flat_map(|p| p.analyze(&data, project, processed, manager)), ); codes } @@ -63,19 +68,27 @@ impl Analyze for Config { impl Analyze for Class { fn analyze( &self, - data: &SqfLintData, + data: &LintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; 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(data, project, processed, manager)) - .collect::>(), + Self::Local { properties, .. } | Self::Root { properties, .. } => { + let data = LintData { + path: self.name().map_or_else( + || data.path.clone(), + |name| format!("{}/{}", data.path, name.value), + ), + }; + properties + .iter() + .flat_map(|p| p.analyze(&data, project, processed, manager)) + .collect::>() + } }); codes } @@ -84,15 +97,20 @@ impl Analyze for Class { impl Analyze for Property { fn analyze( &self, - data: &SqfLintData, + data: &LintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; codes.extend(manager.run(data, project, Some(processed), self)); codes.extend(match self { - Self::Entry { value, .. } => value.analyze(data, project, processed, manager), + Self::Entry { value, .. } => { + let data = LintData { + path: format!("{}.{}", data.path, self.name().value), + }; + value.analyze(&data, project, processed, manager) + } Self::Class(c) => c.analyze(data, project, processed, manager), Self::Delete(_) | Self::MissingSemicolon(_, _) => vec![], }); @@ -103,10 +121,10 @@ impl Analyze for Property { impl Analyze for Value { fn analyze( &self, - data: &SqfLintData, + data: &LintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; codes.extend(manager.run(data, project, Some(processed), self)); @@ -128,10 +146,10 @@ impl Analyze for Value { impl Analyze for Array { fn analyze( &self, - data: &SqfLintData, + data: &LintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; codes.extend(manager.run(data, project, Some(processed), self)); @@ -147,10 +165,10 @@ impl Analyze for Array { impl Analyze for Item { fn analyze( &self, - data: &SqfLintData, + data: &LintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; codes.extend(manager.run(data, project, Some(processed), self)); diff --git a/libs/config/src/lib.rs b/libs/config/src/lib.rs index 48d33d2b..bb3b59a1 100644 --- a/libs/config/src/lib.rs +++ b/libs/config/src/lib.rs @@ -12,7 +12,7 @@ pub mod parse; pub mod rapify; pub use model::*; -use analyze::{Analyze, CfgPatch, ChumskyCode, SqfLintData}; +use analyze::{Analyze, CfgPatch, ChumskyCode, LintData}; use chumsky::Parser; use hemtt_common::version::Version; @@ -52,7 +52,14 @@ pub fn parse( .collect::>(), )?; Ok(ConfigReport { - codes: config.analyze(&SqfLintData {}, project, processed, &manager), + codes: config.analyze( + &LintData { + path: String::new(), + }, + project, + processed, + &manager, + ), patches: config.get_patches(), config, }) diff --git a/libs/config/src/model/ident.rs b/libs/config/src/model/ident.rs index 178422c2..66544b84 100644 --- a/libs/config/src/model/ident.rs +++ b/libs/config/src/model/ident.rs @@ -37,4 +37,10 @@ impl Ident { pub fn is_empty(&self) -> bool { self.value.is_empty() } + + #[must_use] + /// Get the span of the identifier + pub fn span(&self) -> Range { + self.span.clone() + } } diff --git a/libs/config/tests/lints.rs b/libs/config/tests/lints.rs index 9ee39372..b6a67681 100644 --- a/libs/config/tests/lints.rs +++ b/libs/config/tests/lints.rs @@ -29,6 +29,7 @@ lint!(c07_expected_array); lint!(c08_missing_semicolon); lint!(c09_magwell_missing_magazine); lint!(c10_class_missing_braces); +lint!(c11_file_type); fn lint(file: &str) -> String { let folder = std::path::PathBuf::from(ROOT); diff --git a/libs/config/tests/lints/c11_file_type.hpp b/libs/config/tests/lints/c11_file_type.hpp new file mode 100644 index 00000000..dc702210 --- /dev/null +++ b/libs/config/tests/lints/c11_file_type.hpp @@ -0,0 +1,7 @@ +class CfgVehicles { + class MyVehicle { + model = "test.p3d"; + editorPreview = "test.jgp"; + wounds[] = {"would1.pac", "wound2.paa", "wound3.png"}; + }; +}; diff --git a/libs/config/tests/snapshots/lints__config_error_c11_file_type.snap b/libs/config/tests/snapshots/lints__config_error_c11_file_type.snap new file mode 100644 index 00000000..6ab18568 --- /dev/null +++ b/libs/config/tests/snapshots/lints__config_error_c11_file_type.snap @@ -0,0 +1,20 @@ +--- +source: libs/config/tests/lints.rs +expression: lint(stringify! (c11_file_type)) +--- +warning[L-C11UE]: a property that references a file has an unusual file type + ┌─ c11_file_type.hpp:4:31 + │ +4 │ editorPreview = "test.jgp"; + │ ^^^ unusual file type + │ + = note: expected file type jpg + + +warning[L-C11UE]: a property that references a file has an unusual file type + ┌─ c11_file_type.hpp:5:57 + │ +5 │ wounds[] = {"would1.pac", "wound2.paa", "wound3.png"}; + │ ^^^ unusual file type + │ + = note: expected file type paa 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 46a7cb9c..cda944d1 100644 --- a/libs/sqf/src/analyze/lints/s01_command_required_version.rs +++ b/libs/sqf/src/analyze/lints/s01_command_required_version.rs @@ -8,11 +8,11 @@ use hemtt_workspace::{ WorkspacePath, }; -use crate::{analyze::SqfLintData, Statements}; +use crate::{analyze::LintData, Statements}; crate::analyze::lint!(LintS01CommandRequiredVersion); -impl Lint for LintS01CommandRequiredVersion { +impl Lint for LintS01CommandRequiredVersion { fn ident(&self) -> &'static str { "required_version" } @@ -50,13 +50,13 @@ Check [the wiki](https://community.bistudio.com/wiki/Category:Introduced_with_Ar LintConfig::error() } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![Box::new(Runner)] } } pub struct Runner; -impl LintRunner for Runner { +impl LintRunner for Runner { type Target = Statements; fn run( &self, @@ -64,7 +64,7 @@ impl LintRunner for Runner { _config: &hemtt_common::config::LintConfig, processed: Option<&hemtt_workspace::reporting::Processed>, target: &Statements, - data: &SqfLintData, + data: &LintData, ) -> hemtt_workspace::reporting::Codes { let Some(processed) = processed else { return Vec::new(); diff --git a/libs/sqf/src/analyze/lints/s02_event_handlers.rs b/libs/sqf/src/analyze/lints/s02_event_handlers.rs index 572b41e8..67421006 100644 --- a/libs/sqf/src/analyze/lints/s02_event_handlers.rs +++ b/libs/sqf/src/analyze/lints/s02_event_handlers.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ addons::Addon, lint::{AnyLintRunner, Lint, LintGroupRunner}, reporting::{Code, Codes, Diagnostic, Label, Processed, Severity}, WorkspacePath }; -use crate::{analyze::{extract_constant, SqfLintData}, parser::database::Database, BinaryCommand, Expression, Statements, UnaryCommand}; +use crate::{analyze::{extract_constant, LintData}, parser::database::Database, BinaryCommand, Expression, Statements, UnaryCommand}; pub struct LintS02EventInsufficientVersion; -impl Lint for LintS02EventInsufficientVersion { +impl Lint for LintS02EventInsufficientVersion { fn ident(&self) -> &'static str { "event_insufficient_version" } @@ -54,14 +54,14 @@ Check [the wiki](https://community.bistudio.com/wiki/Arma_3:_Event_Handlers) to LintConfig::error() } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![] } } pub struct LintS02EventUnknown; -impl Lint for LintS02EventUnknown { +impl Lint for LintS02EventUnknown { fn ident(&self) -> &'static str { "event_unknown" } @@ -107,14 +107,14 @@ Check [the wiki](https://community.bistudio.com/wiki/Arma_3:_Event_Handlers) to LintConfig::warning() } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![] } } pub struct LintS02EventIncorrectCommand; -impl Lint for LintS02EventIncorrectCommand { +impl Lint for LintS02EventIncorrectCommand { fn ident(&self) -> &'static str { "event_incorrect_command" } @@ -153,13 +153,13 @@ _this addMPEventHandler ["MPHit", { LintConfig::error() } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![] } } pub struct EventHandlerRunner; -impl LintGroupRunner for EventHandlerRunner { +impl LintGroupRunner for EventHandlerRunner { type Target = Statements; fn run( &self, @@ -167,7 +167,7 @@ impl LintGroupRunner for EventHandlerRunner { config: std::collections::HashMap, processed: Option<&Processed>, target: &Statements, - data: &SqfLintData, + data: &LintData, ) -> Codes { let Some(processed) = processed else { return Vec::new(); diff --git a/libs/sqf/src/analyze/lints/s03_static_typename.rs b/libs/sqf/src/analyze/lints/s03_static_typename.rs index 79c0c279..2bbc0d08 100644 --- a/libs/sqf/src/analyze/lints/s03_static_typename.rs +++ b/libs/sqf/src/analyze/lints/s03_static_typename.rs @@ -4,11 +4,11 @@ use float_ord::FloatOrd; use hemtt_common::config::LintConfig; use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, Diagnostic, Processed, Severity}}; -use crate::{analyze::SqfLintData, Expression, NularCommand, UnaryCommand}; +use crate::{analyze::LintData, Expression, NularCommand, UnaryCommand}; crate::analyze::lint!(LintS03StaticTypename); -impl Lint for LintS03StaticTypename { +impl Lint for LintS03StaticTypename { fn ident(&self) -> &'static str { "static_typename" } @@ -46,13 +46,13 @@ if (typeName _myVar == "STRING") then { LintConfig::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 = Expression; fn run( @@ -61,7 +61,7 @@ impl LintRunner for Runner { config: &LintConfig, processed: Option<&hemtt_workspace::reporting::Processed>, target: &Self::Target, - _data: &SqfLintData, + _data: &LintData, ) -> hemtt_workspace::reporting::Codes { let Some(processed) = processed else { return Vec::new(); diff --git a/libs/sqf/src/analyze/lints/s04_command_case.rs b/libs/sqf/src/analyze/lints/s04_command_case.rs index b5e6a4cf..956c43c6 100644 --- a/libs/sqf/src/analyze/lints/s04_command_case.rs +++ b/libs/sqf/src/analyze/lints/s04_command_case.rs @@ -3,11 +3,11 @@ use std::{ops::Range, sync::Arc}; use hemtt_common::config::{LintConfig, ProjectConfig}; use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, Codes, Diagnostic, Processed, Severity}}; -use crate::{analyze::SqfLintData, Expression}; +use crate::{analyze::LintData, Expression}; crate::analyze::lint!(LintS04CommandCase); -impl Lint for LintS04CommandCase { +impl Lint for LintS04CommandCase { fn ident(&self) -> &'static str { "command_case" } @@ -49,13 +49,13 @@ private _leaky = getWaterLeakiness vehicle player; LintConfig::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 = Expression; fn run( @@ -64,7 +64,7 @@ impl LintRunner for Runner { config: &LintConfig, processed: Option<&Processed>, target: &Self::Target, - data: &SqfLintData, + data: &LintData, ) -> Codes { let Some(processed) = processed else { return Vec::new(); diff --git a/libs/sqf/src/analyze/lints/s05_if_assign.rs b/libs/sqf/src/analyze/lints/s05_if_assign.rs index 3ce051bd..f34a0706 100644 --- a/libs/sqf/src/analyze/lints/s05_if_assign.rs +++ b/libs/sqf/src/analyze/lints/s05_if_assign.rs @@ -3,11 +3,11 @@ use std::{ops::Range, sync::Arc}; use hemtt_common::config::LintConfig; use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, Diagnostic, Processed, Severity}}; -use crate::{analyze::{extract_constant, SqfLintData}, BinaryCommand, Expression, UnaryCommand}; +use crate::{analyze::{extract_constant, LintData}, BinaryCommand, Expression, UnaryCommand}; crate::analyze::lint!(LintS05IfAssign); -impl Lint for LintS05IfAssign { +impl Lint for LintS05IfAssign { fn ident(&self) -> &'static str { "if_assign" } @@ -49,14 +49,14 @@ private _x = ["orange", "apple"] select _myVar; LintConfig::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 = Expression; fn run( @@ -65,7 +65,7 @@ impl LintRunner for Runner { config: &LintConfig, processed: Option<&hemtt_workspace::reporting::Processed>, target: &Self::Target, - _data: &SqfLintData, + _data: &LintData, ) -> hemtt_workspace::reporting::Codes { let Some(processed) = processed else { return Vec::new(); 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 e126bff7..1c24aaa7 100644 --- a/libs/sqf/src/analyze/lints/s06_find_in_str.rs +++ b/libs/sqf/src/analyze/lints/s06_find_in_str.rs @@ -4,11 +4,11 @@ use float_ord::FloatOrd; use hemtt_common::config::LintConfig; use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, Diagnostic, Processed, Severity}}; -use crate::{analyze::SqfLintData, BinaryCommand, Expression, UnaryCommand}; +use crate::{analyze::LintData, BinaryCommand, Expression, UnaryCommand}; crate::analyze::lint!(LintS06FindInStr); -impl Lint for LintS06FindInStr { +impl Lint for LintS06FindInStr { fn ident(&self) -> &'static str { "find_in_str" } @@ -42,13 +42,13 @@ The `in` command is faster than `find` when searching for a substring in a strin LintConfig::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 = Expression; fn run( @@ -57,7 +57,7 @@ impl LintRunner for Runner { config: &LintConfig, processed: Option<&hemtt_workspace::reporting::Processed>, target: &Self::Target, - _data: &SqfLintData, + _data: &LintData, ) -> hemtt_workspace::reporting::Codes { let Some(processed) = processed else { return Vec::new(); 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 ca069dbc..fb260072 100644 --- a/libs/sqf/src/analyze/lints/s07_select_parse_number.rs +++ b/libs/sqf/src/analyze/lints/s07_select_parse_number.rs @@ -5,11 +5,11 @@ use float_ord::FloatOrd; use hemtt_common::config::{LintConfig, ProjectConfig}; use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, Codes, Diagnostic, Processed, Severity}}; -use crate::{analyze::SqfLintData, parser::database::Database, BinaryCommand, Expression}; +use crate::{analyze::LintData, parser::database::Database, BinaryCommand, Expression}; crate::analyze::lint!(LintS07SelectParseNumber); -impl Lint for LintS07SelectParseNumber { +impl Lint for LintS07SelectParseNumber { fn ident(&self) -> &'static str { "select_parse_number" } @@ -51,13 +51,13 @@ Using `select` on an array with 0 and 1 can be replaced with `parseNumber` for b LintConfig::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 = Expression; fn run( @@ -66,7 +66,7 @@ impl LintRunner for Runner { config: &LintConfig, processed: Option<&Processed>, target: &Self::Target, - data: &SqfLintData, + data: &LintData, ) -> Codes { let Some(processed) = processed else { return Vec::new(); diff --git a/libs/sqf/src/analyze/lints/s08_format_args.rs b/libs/sqf/src/analyze/lints/s08_format_args.rs index e29f6966..e56e36a5 100644 --- a/libs/sqf/src/analyze/lints/s08_format_args.rs +++ b/libs/sqf/src/analyze/lints/s08_format_args.rs @@ -3,11 +3,11 @@ use std::{cmp::Ordering, ops::Range, sync::Arc}; use hemtt_common::config::LintConfig; use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, Codes, Diagnostic, Processed, Severity}}; -use crate::{analyze::SqfLintData, Expression, UnaryCommand}; +use crate::{analyze::LintData, Expression, UnaryCommand}; crate::analyze::lint!(LintS08FormatArgs); -impl Lint for LintS08FormatArgs { +impl Lint for LintS08FormatArgs { fn ident(&self) -> &'static str { "format_args" } @@ -53,13 +53,13 @@ The `format` and `formatText` commands requires the correct number of arguments 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 = crate::Expression; fn run( @@ -68,7 +68,7 @@ impl LintRunner for Runner { config: &LintConfig, processed: Option<&hemtt_workspace::reporting::Processed>, target: &Self::Target, - _data: &SqfLintData, + _data: &LintData, ) -> Codes { let Some(processed) = processed else { return Vec::new(); diff --git a/libs/sqf/src/analyze/lints/s09_banned_command.rs b/libs/sqf/src/analyze/lints/s09_banned_command.rs index 130368f7..e1675d4c 100644 --- a/libs/sqf/src/analyze/lints/s09_banned_command.rs +++ b/libs/sqf/src/analyze/lints/s09_banned_command.rs @@ -3,11 +3,11 @@ use std::{ops::Range, sync::Arc}; use hemtt_common::config::{LintConfig, ProjectConfig}; use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, Codes, Diagnostic, Processed, Severity}}; -use crate::{analyze::SqfLintData, Expression}; +use crate::{analyze::LintData, Expression}; crate::analyze::lint!(LintS09BannedCommand); -impl Lint for LintS09BannedCommand { +impl Lint for LintS09BannedCommand { fn ident(&self) -> &'static str { "banned_commands" } @@ -56,13 +56,13 @@ Checks for usage of broken or banned commands."# 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 = Expression; fn run( @@ -71,7 +71,7 @@ impl LintRunner for Runner { config: &LintConfig, processed: Option<&Processed>, target: &Self::Target, - data: &SqfLintData, + data: &LintData, ) -> Codes { let Some(processed) = processed else { return Vec::new(); 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 ab0b60b7..2b3aeabd 100644 --- a/libs/sqf/src/analyze/lints/s11_if_not_else.rs +++ b/libs/sqf/src/analyze/lints/s11_if_not_else.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{Code, Codes, Diagnostic, Processed, Severity}, }; -use crate::{analyze::SqfLintData, BinaryCommand, Expression, UnaryCommand}; +use crate::{analyze::LintData, BinaryCommand, Expression, UnaryCommand}; crate::analyze::lint!(LintS11IfNotElse); -impl Lint for LintS11IfNotElse { +impl Lint for LintS11IfNotElse { fn ident(&self) -> &'static str { "if_not_else" } @@ -42,13 +42,13 @@ if (alive player) then { objNull } else { player }; LintConfig::help().with_enabled(false) } - 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::Expression; fn run( @@ -57,7 +57,7 @@ impl LintRunner for Runner { config: &LintConfig, processed: Option<&hemtt_workspace::reporting::Processed>, target: &Self::Target, - _data: &SqfLintData, + _data: &LintData, ) -> Codes { let Some(processed) = processed else { return Vec::new(); 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 433f3070..38346497 100644 --- a/libs/sqf/src/analyze/lints/s17_var_all_caps.rs +++ b/libs/sqf/src/analyze/lints/s17_var_all_caps.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{Code, Codes, Diagnostic, Label, Processed, Severity, Symbol}, }; -use crate::{analyze::SqfLintData, Expression}; +use crate::{analyze::LintData, Expression}; crate::analyze::lint!(LintS17VarAllCaps); -impl Lint for LintS17VarAllCaps { +impl Lint for LintS17VarAllCaps { fn ident(&self) -> &'static str { "var_all_caps" } @@ -50,13 +50,13 @@ Variables that are all caps are usually reserved for macros. This should should fn default_config(&self) -> LintConfig { LintConfig::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 = crate::Expression; fn run( @@ -65,7 +65,7 @@ impl LintRunner for Runner { config: &LintConfig, processed: Option<&hemtt_workspace::reporting::Processed>, target: &Self::Target, - _data: &SqfLintData, + _data: &LintData, ) -> Codes { let Some(processed) = processed else { return Vec::new(); 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 c47bc5b3..20a85348 100644 --- a/libs/sqf/src/analyze/lints/s18_in_vehicle_check.rs +++ b/libs/sqf/src/analyze/lints/s18_in_vehicle_check.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{Code, Codes, Diagnostic, Processed, Severity}, }; -use crate::{analyze::SqfLintData, BinaryCommand, Expression, NularCommand, UnaryCommand}; +use crate::{analyze::LintData, BinaryCommand, Expression, NularCommand, UnaryCommand}; crate::analyze::lint!(LintS18InVehicleCheck); -impl Lint for LintS18InVehicleCheck { +impl Lint for LintS18InVehicleCheck { fn ident(&self) -> &'static str { "in_vehicle_check" } @@ -45,13 +45,13 @@ Using `isNull objectParent x` is faster and more reliable than `vehicle x == x` LintConfig::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 = crate::Expression; fn run( @@ -60,7 +60,7 @@ impl LintRunner for Runner { config: &LintConfig, processed: Option<&hemtt_workspace::reporting::Processed>, target: &Self::Target, - _data: &SqfLintData, + _data: &LintData, ) -> Codes { let Some(processed) = processed else { return Vec::new(); 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 91789eb1..ca1180b8 100644 --- a/libs/sqf/src/analyze/lints/s20_bool_static_comparison.rs +++ b/libs/sqf/src/analyze/lints/s20_bool_static_comparison.rs @@ -3,11 +3,11 @@ use std::{ops::Range, sync::Arc}; use hemtt_common::config::LintConfig; use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, Codes, Diagnostic, Processed, Severity}}; -use crate::{analyze::SqfLintData, BinaryCommand, Expression}; +use crate::{analyze::LintData, BinaryCommand, Expression}; crate::analyze::lint!(LintS20BoolStaticComparison); -impl Lint for LintS20BoolStaticComparison { +impl Lint for LintS20BoolStaticComparison { fn ident(&self) -> &'static str { "bool_static_comparison" } @@ -40,13 +40,13 @@ if (!_y) then {}; LintConfig::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 = crate::Expression; fn run( @@ -55,7 +55,7 @@ impl LintRunner for Runner { config: &LintConfig, processed: Option<&hemtt_workspace::reporting::Processed>, target: &Self::Target, - _data: &SqfLintData, + _data: &LintData, ) -> Codes { let Some(processed) = processed else { return Vec::new(); diff --git a/libs/sqf/src/analyze/lints/s21_invalid_comparisons.rs b/libs/sqf/src/analyze/lints/s21_invalid_comparisons.rs index e636049f..4ecbec95 100644 --- a/libs/sqf/src/analyze/lints/s21_invalid_comparisons.rs +++ b/libs/sqf/src/analyze/lints/s21_invalid_comparisons.rs @@ -7,11 +7,11 @@ use hemtt_workspace::{ reporting::{Code, Codes, Diagnostic, Label, Processed, Severity}, }; -use crate::{analyze::SqfLintData, BinaryCommand, Expression, Statement, UnaryCommand}; +use crate::{analyze::LintData, BinaryCommand, Expression, Statement, UnaryCommand}; crate::analyze::lint!(LintS21InvalidComparisons); -impl Lint for LintS21InvalidComparisons { +impl Lint for LintS21InvalidComparisons { fn ident(&self) -> &'static str { "invalid_comparisons" } @@ -45,13 +45,13 @@ This lint checks for if statements with impossible or overlapping conditions. Th LintConfig::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 = crate::Expression; fn run( @@ -60,7 +60,7 @@ impl LintRunner for Runner { config: &LintConfig, processed: Option<&hemtt_workspace::reporting::Processed>, target: &Self::Target, - _data: &SqfLintData, + _data: &LintData, ) -> Codes { let Some(processed) = processed else { return Vec::new(); diff --git a/libs/sqf/src/analyze/lints/s22_this_call.rs b/libs/sqf/src/analyze/lints/s22_this_call.rs index 1e9cc50e..04e11ad4 100644 --- a/libs/sqf/src/analyze/lints/s22_this_call.rs +++ b/libs/sqf/src/analyze/lints/s22_this_call.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{Code, Codes, Diagnostic, Processed, Severity}, }; -use crate::{analyze::SqfLintData, BinaryCommand, Expression}; +use crate::{analyze::LintData, BinaryCommand, Expression}; crate::analyze::lint!(LintS22ThisCall); -impl Lint for LintS22ThisCall { +impl Lint for LintS22ThisCall { fn ident(&self) -> &'static str { "this_call" } @@ -45,13 +45,13 @@ When using `call`, the called code will inherit `_this` from the calling scope. LintConfig::help().with_enabled(false) } - 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::Expression; fn run( @@ -60,7 +60,7 @@ impl LintRunner for Runner { config: &LintConfig, processed: Option<&hemtt_workspace::reporting::Processed>, target: &Self::Target, - _data: &SqfLintData, + _data: &LintData, ) -> Codes { let Some(processed) = processed else { return Vec::new(); 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 3d54bc48..91c35855 100644 --- a/libs/sqf/src/analyze/lints/s23_reassign_reserved_variable.rs +++ b/libs/sqf/src/analyze/lints/s23_reassign_reserved_variable.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{Code, Codes, Diagnostic, Label, Processed, Severity}, WorkspacePath, }; -use crate::{analyze::SqfLintData, BinaryCommand, Expression, Statement, UnaryCommand}; +use crate::{analyze::LintData, BinaryCommand, Expression, Statement, UnaryCommand}; crate::analyze::lint!(LintS23ReassignReservedVariable); -impl Lint for LintS23ReassignReservedVariable { +impl Lint for LintS23ReassignReservedVariable { fn ident(&self) -> &'static str { "reasign_reserved_variable" } @@ -49,7 +49,7 @@ Reassigning reserved variables can lead to unintentional behavior. LintConfig::error() } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![Box::new(StatementsRunner {}), Box::new(ExpressionRunner {})] } } @@ -59,7 +59,7 @@ static RESERVED: [&str; 8] = [ ]; struct StatementsRunner {} -impl LintRunner for StatementsRunner { +impl LintRunner for StatementsRunner { type Target = crate::Statements; #[allow(clippy::significant_drop_tightening)] @@ -69,7 +69,7 @@ impl LintRunner for StatementsRunner { config: &LintConfig, processed: Option<&hemtt_workspace::reporting::Processed>, target: &Self::Target, - _data: &SqfLintData, + _data: &LintData, ) -> Codes { let Some(processed) = processed else { return Vec::new(); @@ -122,7 +122,7 @@ impl LintRunner for StatementsRunner { } struct ExpressionRunner {} -impl LintRunner for ExpressionRunner { +impl LintRunner for ExpressionRunner { type Target = crate::Expression; #[allow(clippy::significant_drop_tightening)] @@ -132,7 +132,7 @@ impl LintRunner for ExpressionRunner { config: &LintConfig, processed: Option<&hemtt_workspace::reporting::Processed>, target: &Self::Target, - _data: &SqfLintData, + _data: &LintData, ) -> Codes { let Some(processed) = processed else { return Vec::new(); diff --git a/libs/sqf/src/analyze/lints/s24_marker_spam.rs b/libs/sqf/src/analyze/lints/s24_marker_spam.rs index eb47d2b0..99fb800b 100644 --- a/libs/sqf/src/analyze/lints/s24_marker_spam.rs +++ b/libs/sqf/src/analyze/lints/s24_marker_spam.rs @@ -6,11 +6,11 @@ use hemtt_workspace::{ reporting::{Code, Codes, Diagnostic, Label, Processed, Severity}, WorkspacePath, }; -use crate::{analyze::SqfLintData, BinaryCommand, Expression, Statement}; +use crate::{analyze::LintData, BinaryCommand, Expression, Statement}; crate::analyze::lint!(LintS24MarkerSpam); -impl Lint for LintS24MarkerSpam { +impl Lint for LintS24MarkerSpam { fn ident(&self) -> &'static str { "marker_update_spam" } @@ -52,13 +52,13 @@ Using the `setMarker*Local` on all calls except the last one will reduce the amo LintConfig::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 = crate::Statements; fn run( @@ -67,7 +67,7 @@ impl LintRunner for Runner { config: &LintConfig, processed: Option<&hemtt_workspace::reporting::Processed>, target: &Self::Target, - _data: &SqfLintData, + _data: &LintData, ) -> Codes { let Some(processed) = processed else { return Vec::new(); diff --git a/libs/sqf/src/analyze/mod.rs b/libs/sqf/src/analyze/mod.rs index 81e0c624..369d2181 100644 --- a/libs/sqf/src/analyze/mod.rs +++ b/libs/sqf/src/analyze/mod.rs @@ -41,7 +41,7 @@ pub fn analyze( addon: Arc, database: Arc, ) -> Codes { - let mut manager: LintManager = LintManager::new( + let mut manager: LintManager = LintManager::new( project.map_or_else(Default::default, |project| project.lints().sqf().clone()), ); if let Err(lint_errors) = @@ -62,15 +62,15 @@ pub fn analyze( statements.analyze(&(addon, database), project, processed, &manager) } -pub type SqfLintData = (Arc, Arc); +pub type LintData = (Arc, Arc); pub trait Analyze: Sized + 'static { fn analyze( &self, - data: &SqfLintData, + data: &LintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; codes.extend(manager.run(data, project, Some(processed), self)); @@ -85,10 +85,10 @@ impl Analyze for BinaryCommand {} impl Analyze for Statements { fn analyze( &self, - data: &SqfLintData, + data: &LintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; codes.extend(manager.run(data, project, Some(processed), self)); @@ -102,10 +102,10 @@ impl Analyze for Statements { impl Analyze for Statement { fn analyze( &self, - data: &SqfLintData, + data: &LintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; codes.extend(manager.run(data, project, Some(processed), self)); @@ -123,10 +123,10 @@ impl Analyze for Statement { impl Analyze for Expression { fn analyze( &self, - data: &SqfLintData, + data: &LintData, project: Option<&ProjectConfig>, processed: &Processed, - manager: &LintManager, + manager: &LintManager, ) -> Codes { let mut codes = vec![]; codes.extend(manager.run(data, project, Some(processed), self)); diff --git a/libs/stringtable/src/analyze/lints/01_sorted.rs b/libs/stringtable/src/analyze/lints/01_sorted.rs index 95a468d6..8b930aff 100644 --- a/libs/stringtable/src/analyze/lints/01_sorted.rs +++ b/libs/stringtable/src/analyze/lints/01_sorted.rs @@ -3,11 +3,11 @@ use std::sync::Arc; use hemtt_common::config::LintConfig; use hemtt_workspace::{lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, Codes, Diagnostic, Severity}, WorkspacePath}; -use crate::{analyze::SqfLintData, Project}; +use crate::{analyze::LintData, Project}; crate::analyze::lint!(LintL01Sorted); -impl Lint for LintL01Sorted { +impl Lint for LintL01Sorted { fn ident(&self) -> &'static str { "sorted" } @@ -28,7 +28,7 @@ impl Lint for LintL01Sorted { LintConfig::warning() } - fn runners(&self) -> Vec>> { + fn runners(&self) -> Vec>> { vec![Box::new(Runner)] } } @@ -36,7 +36,7 @@ impl Lint for LintL01Sorted { pub type StringtableData = (Project, WorkspacePath, String); pub struct Runner; -impl LintRunner for Runner { +impl LintRunner for Runner { type Target = Vec; fn run( &self, @@ -44,7 +44,7 @@ impl LintRunner for Runner { config: &hemtt_common::config::LintConfig, _processed: Option<&hemtt_workspace::reporting::Processed>, target: &Vec, - _data: &SqfLintData, + _data: &LintData, ) -> Codes { let mut unsorted = Vec::new(); let mut codes: Codes = Vec::new(); diff --git a/libs/stringtable/src/analyze/mod.rs b/libs/stringtable/src/analyze/mod.rs index 475495e9..a7a8d4d0 100644 --- a/libs/stringtable/src/analyze/mod.rs +++ b/libs/stringtable/src/analyze/mod.rs @@ -8,7 +8,7 @@ pub mod lints { lint_manager!(stringtable, vec![]); -pub struct SqfLintData {} +pub struct LintData {} pub fn lint_one(addon: &StringtableData, project: Option<&ProjectConfig>) -> Codes { let mut manager = LintManager::new(project.map_or_else(Default::default, |project| { @@ -22,7 +22,7 @@ pub fn lint_one(addon: &StringtableData, project: Option<&ProjectConfig>) -> Cod ) { return e; } - manager.run(&SqfLintData {}, project, None, addon) + manager.run(&LintData {}, project, None, addon) } #[allow(clippy::ptr_arg)] // Needed for &Vec for &dyn Any @@ -38,5 +38,5 @@ pub fn lint_all(addons: &Vec, project: Option<&ProjectConfig>) ) { return e; } - manager.run(&SqfLintData {}, project, None, addons) + manager.run(&LintData {}, project, None, addons) } diff --git a/libs/workspace/src/lint/macros.rs b/libs/workspace/src/lint/macros.rs index ad91cc7d..5e2a65ef 100644 --- a/libs/workspace/src/lint/macros.rs +++ b/libs/workspace/src/lint/macros.rs @@ -4,7 +4,7 @@ macro_rules! lint_manager { $crate::paste::paste! { #[linkme::distributed_slice] pub static [<$ident:upper _LINTS>]: [std::sync::LazyLock< - std::sync::Arc>>, + std::sync::Arc>>, >]; #[allow(unused_macros)] @@ -14,7 +14,7 @@ macro_rules! lint_manager { pub struct $name; #[linkme::distributed_slice(super::super::[<$ident:upper _LINTS>])] static LINT_ADD: std::sync::LazyLock< - std::sync::Arc>>, + std::sync::Arc>>, > = std::sync::LazyLock::new(|| std::sync::Arc::new(Box::new($name))); }; } @@ -24,7 +24,7 @@ macro_rules! lint_manager { pub fn lint_check( config: std::collections::HashMap, ) -> $crate::reporting::Codes { - let mut manager: $crate::lint::LintManager = + 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::>()) @@ -32,8 +32,8 @@ macro_rules! lint_manager { return lint_errors; } let groups: Vec<( - $crate::lint::Lints, - Box>, + $crate::lint::Lints, + Box>, )> = $groups; for group in groups { if let Err(lint_errors) = manager.push_group(group.0, group.1) { diff --git a/libs/workspace/src/reporting/diagnostic/mod.rs b/libs/workspace/src/reporting/diagnostic/mod.rs index 1c6088e0..20599409 100644 --- a/libs/workspace/src/reporting/diagnostic/mod.rs +++ b/libs/workspace/src/reporting/diagnostic/mod.rs @@ -58,6 +58,40 @@ impl Diagnostic { diag } + pub fn from_code_processed_skip_macros( + code: &impl Code, + mut span: std::ops::Range, + processed: &crate::reporting::Processed, + ) -> Option { + let mut diag = Self::new(code.ident(), code.message()).set_severity(code.severity()); + + // Error out out bounds, will never show, just use last char + if span.start == processed.as_str().len() { + span.start = processed.as_str().len() - 1; + span.end = processed.as_str().len() - 1; + } + let map_start = processed.mapping_no_macros(span.start)?; + let map_file = processed.source(map_start.source())?; + diag.labels.push( + Label::primary( + map_file.0.clone(), + map_start.original_start()..map_start.original_end(), + ) + .with_message(code.label_message()), + ); + 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); + } + Some(diag) + } + pub fn from_code_processed( code: &impl Code, mut span: std::ops::Range, diff --git a/libs/workspace/src/reporting/processed.rs b/libs/workspace/src/reporting/processed.rs index 35f2c891..c39b96ac 100644 --- a/libs/workspace/src/reporting/processed.rs +++ b/libs/workspace/src/reporting/processed.rs @@ -258,6 +258,15 @@ impl Processed { self.mappings(offset).last().copied() } + #[must_use] + /// Get the deepest tree mapping at a position in the stringified output + pub fn mapping_no_macros(&self, offset: usize) -> Option<&Mapping> { + self.mappings(offset) + .into_iter() + .rev() + .find(|m| !m.was_macro) + } + #[must_use] /// Get the macros defined pub const fn macros(&self) -> &HashMap> {