-
-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
sqf: Add lint for vars that are ALL_CAPS and not a macro (#793)
- Loading branch information
1 parent
1e70aa7
commit c823e30
Showing
8 changed files
with
251 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
use std::{ops::Range, sync::Arc}; | ||
|
||
use hemtt_common::{config::LintConfig, similar_values}; | ||
use hemtt_workspace::{ | ||
lint::{AnyLintRunner, Lint, LintRunner}, | ||
reporting::{Code, Codes, Diagnostic, Label, Processed, Severity, Symbol}, | ||
}; | ||
|
||
use crate::{analyze::SqfLintData, Expression}; | ||
|
||
crate::lint!(LintS17VarAllCaps); | ||
|
||
impl Lint<SqfLintData> for LintS17VarAllCaps { | ||
fn ident(&self) -> &str { | ||
"var_all_caps" | ||
} | ||
|
||
fn sort(&self) -> u32 { | ||
170 | ||
} | ||
|
||
fn description(&self) -> &str { | ||
"Checks for global variables that are ALL_CAPS and may actually be a undefined macro" | ||
} | ||
|
||
fn documentation(&self) -> &str { | ||
r#"### Configuration | ||
- **ignore**: An array of vars to ignore | ||
```toml | ||
[lints.sqf.var_all_caps] | ||
options.ignore = [ | ||
"XMOD_TEST", "MYMOD_*", | ||
] | ||
``` | ||
### Example | ||
**Incorrect** | ||
```sqf | ||
private _z = _y + DO_NOT_EXIST; | ||
``` | ||
### Explanation | ||
Variables that are all caps are usually reserved for macros. This should should help prevent any accidental typos or uses before definitions when using macros. | ||
."# | ||
} | ||
fn default_config(&self) -> LintConfig { | ||
LintConfig::help() | ||
} | ||
fn runners(&self) -> Vec<Box<dyn AnyLintRunner<SqfLintData>>> { | ||
vec![Box::new(Runner)] | ||
} | ||
} | ||
|
||
struct Runner; | ||
impl LintRunner<SqfLintData> for Runner { | ||
type Target = crate::Expression; | ||
|
||
fn run( | ||
&self, | ||
_project: Option<&hemtt_common::config::ProjectConfig>, | ||
config: &LintConfig, | ||
processed: Option<&hemtt_workspace::reporting::Processed>, | ||
target: &Self::Target, | ||
_data: &SqfLintData, | ||
) -> Codes { | ||
let Some(processed) = processed else { | ||
return Vec::new(); | ||
}; | ||
let Expression::Variable(var, span) = target else { | ||
return Vec::new(); | ||
}; | ||
if var.starts_with('_') || &var.to_ascii_uppercase() != var || var == "SLX_XEH_COMPILE_NEW" | ||
{ | ||
return Vec::new(); | ||
} | ||
if let Some(toml::Value::Array(ignore)) = config.option("ignore") { | ||
if ignore.iter().any(|i| { | ||
let s = i.as_str().unwrap_or_default(); | ||
s == var || (s.ends_with('*') && var.starts_with(&s[0..s.len() - 1])) | ||
}) { | ||
return Vec::new(); | ||
} | ||
} | ||
vec![Arc::new(CodeS17VarAllCaps::new( | ||
span.clone(), | ||
var.clone(), | ||
processed, | ||
config.severity(), | ||
))] | ||
} | ||
} | ||
|
||
#[allow(clippy::module_name_repetitions)] | ||
pub struct CodeS17VarAllCaps { | ||
span: Range<usize>, | ||
ident: String, | ||
similar: Vec<String>, | ||
severity: Severity, | ||
diagnostic: Option<Diagnostic>, | ||
} | ||
|
||
impl Code for CodeS17VarAllCaps { | ||
fn ident(&self) -> &'static str { | ||
"L-S17" | ||
} | ||
|
||
fn link(&self) -> Option<&str> { | ||
Some("/analysis/sqf.html#var_all_caps") | ||
} | ||
|
||
fn severity(&self) -> Severity { | ||
self.severity | ||
} | ||
|
||
fn message(&self) -> String { | ||
format!("Variable should not be all caps: {}", self.ident) | ||
} | ||
|
||
fn note(&self) -> Option<String> { | ||
Some("All caps variables are usually reserved for macros".to_string()) | ||
} | ||
|
||
fn label_message(&self) -> String { | ||
"All caps variable".to_string() | ||
} | ||
|
||
fn help(&self) -> Option<String> { | ||
if self.similar.is_empty() { | ||
None | ||
} else { | ||
Some(format!("did you mean `{}`?", self.similar.join("`, `"))) | ||
} | ||
} | ||
|
||
fn diagnostic(&self) -> Option<Diagnostic> { | ||
self.diagnostic.clone() | ||
} | ||
} | ||
|
||
impl CodeS17VarAllCaps { | ||
#[must_use] | ||
pub fn new(span: Range<usize>, ident: String, processed: &Processed, severity: Severity) -> Self { | ||
Self { | ||
similar: similar_values( | ||
&ident, | ||
&processed.macros().keys().map(std::string::String::as_str).collect::<Vec<_>>(), | ||
) | ||
.iter() | ||
.map(std::string::ToString::to_string) | ||
.collect(), | ||
span, | ||
ident, | ||
severity, | ||
diagnostic: None, | ||
} | ||
.generate_processed(processed) | ||
} | ||
|
||
fn generate_processed(mut self, processed: &Processed) -> Self { | ||
let Some(mut diagnostic) = Diagnostic::new_for_processed(&self, self.span.clone(), processed) else { | ||
return self; | ||
}; | ||
self.diagnostic = Some(diagnostic.clone()); | ||
let mut mappings = processed.mappings(self.span.start); | ||
mappings.pop(); | ||
let symbol = Symbol::Word(self.ident.clone()); | ||
let Some(mapping) = mappings | ||
.iter() | ||
.find(|m| { | ||
m.token().symbol() == &symbol | ||
}) else { | ||
return self; | ||
}; | ||
if let Some(l) = diagnostic.labels.get_mut(0) { *l = l.clone().with_message("Used in macro here"); } | ||
diagnostic.labels.push( | ||
Label::primary( | ||
mapping.original().path().clone(), | ||
mapping.original().span(), | ||
) | ||
.with_message("All caps variable"), | ||
); | ||
self.diagnostic = Some(diagnostic); | ||
self | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#define EXIST 1 | ||
#define TYPO 2 | ||
#define NESTED systemChat UNDEFINED | ||
|
||
private _x = 1 + EXIST; | ||
private _y = _x + AM_IGNORED; | ||
private _z = _y + DO_NOT_EXIST; | ||
private _w = _z + TPYO; | ||
NESTED; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
[0m[1m[38;5;14mhelp[L-S17][0m[1m: Variable should not be all caps: DO_NOT_EXIST[0m | ||
[0m[36m┌─[0m source.sqf:7:19 | ||
[0m[36m│[0m | ||
[0m[36m7[0m [0m[36m│[0m private _z = _y + [0m[36mDO_NOT_EXIST[0m; | ||
[0m[36m│[0m [0m[36m^^^^^^^^^^^^[0m [0m[36mAll caps variable[0m | ||
[0m[36m│[0m | ||
[0m[36m=[0m [36mnote[0m: All caps variables are usually reserved for macros | ||
|
||
|
||
[0m[1m[38;5;14mhelp[L-S17][0m[1m: Variable should not be all caps: TPYO[0m | ||
[0m[36m┌─[0m source.sqf:8:19 | ||
[0m[36m│[0m | ||
[0m[36m8[0m [0m[36m│[0m private _w = _z + [0m[36mTPYO[0m; | ||
[0m[36m│[0m [0m[36m^^^^[0m [0m[36mAll caps variable[0m | ||
[0m[36m│[0m | ||
[0m[36m=[0m [36mnote[0m: All caps variables are usually reserved for macros | ||
[0m[36m=[0m [33mhelp[0m: did you mean `TYPO`? | ||
|
||
|
||
[0m[1m[38;5;14mhelp[L-S17][0m[1m: Variable should not be all caps: UNDEFINED[0m | ||
[0m[36m┌─[0m source.sqf:3:27 | ||
[0m[36m│[0m | ||
[0m[36m3[0m [0m[36m│[0m #define NESTED systemChat [0m[36mUNDEFINED[0m | ||
[0m[36m│[0m [0m[36m^^^^^^^^^[0m [0m[36mAll caps variable[0m | ||
[0m[36m·[0m | ||
[0m[36m9[0m [0m[36m│[0m [0m[36mNESTED[0m; | ||
[0m[36m│[0m [0m[36m^^^^^^[0m [0m[36mUsed in macro here[0m | ||
[0m[36m│[0m | ||
[0m[36m=[0m [36mnote[0m: All caps variables are usually reserved for macros | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters