selene_lib/lints/
global_usage.rs1use super::*;
2use std::collections::HashSet;
3
4use full_moon::ast::Ast;
5use regex::Regex;
6use serde::Deserialize;
7
8fn is_global(name: &str, roblox: bool) -> bool {
9 (roblox && name == "shared") || name == "_G"
10}
11
12#[derive(Clone, Default, Deserialize)]
13#[serde(default)]
14pub struct GlobalConfig {
15 ignore_pattern: Option<String>,
16}
17
18pub struct GlobalLint {
19 ignore_pattern: Option<Regex>,
20}
21
22impl Lint for GlobalLint {
23 type Config = GlobalConfig;
24 type Error = regex::Error;
25
26 const SEVERITY: Severity = Severity::Warning;
27 const LINT_TYPE: LintType = LintType::Complexity;
28
29 fn new(config: Self::Config) -> Result<Self, Self::Error> {
30 Ok(GlobalLint {
31 ignore_pattern: config
32 .ignore_pattern
33 .map(|ignore_pattern| Regex::new(&ignore_pattern))
34 .transpose()?,
35 })
36 }
37
38 fn pass(&self, _: &Ast, context: &Context, ast_context: &AstContext) -> Vec<Diagnostic> {
39 let mut checked = HashSet::new(); ast_context
42 .scope_manager
43 .references
44 .iter()
45 .filter(|(_, reference)| {
46 if !checked.contains(&reference.identifier) {
47 checked.insert(reference.identifier);
48
49 let matches_ignore_pattern = match &self.ignore_pattern {
50 Some(ignore_pattern) => match reference
51 .indexing
52 .as_ref()
53 .and_then(|indexing| indexing.first())
54 .and_then(|index_entry| index_entry.static_name.as_ref())
55 {
56 Some(name) => ignore_pattern
58 .is_match(name.to_string().trim_end_matches(char::is_whitespace)),
59 None => false,
60 },
61 None => false,
62 };
63
64 is_global(&reference.name, context.is_roblox())
65 && !matches_ignore_pattern
66 && reference.resolved.is_none()
67 } else {
68 false
69 }
70 })
71 .map(|(_, reference)| {
72 Diagnostic::new(
73 "global_usage",
74 format!(
75 "use of `{}` is not allowed, structure your code in a more idiomatic way",
76 reference.name
77 ),
78 Label::new(reference.identifier),
79 )
80 })
81 .collect()
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::{super::test_util::test_lint, *};
88
89 #[test]
90 fn test_global_usage() {
91 test_lint(
92 GlobalLint::new(GlobalConfig::default()).unwrap(),
93 "global_usage",
94 "global_usage",
95 );
96 }
97
98 #[test]
99 fn test_invalid_regex() {
100 assert!(GlobalLint::new(GlobalConfig {
101 ignore_pattern: Some("(".to_owned()),
102 })
103 .is_err());
104 }
105
106 #[test]
107 fn test_global_usage_ignore() {
108 test_lint(
109 GlobalLint::new(GlobalConfig {
110 ignore_pattern: Some("^_.*_$".to_owned()),
111 })
112 .unwrap(),
113 "global_usage",
114 "global_usage_ignore",
115 );
116 }
117}