shadowplay/puppet_pp_lint/
lint_argument.rs

1use crate::puppet_parser::range::Range;
2use serde::{Deserialize, Serialize};
3
4use crate::puppet_pp_lint::lint::{EarlyLintPass, LintError, LintPass};
5
6#[derive(Clone, Serialize, Deserialize)]
7pub struct ArgumentLooksSensitive {
8    #[serde(with = "serde_regex")]
9    regex: regex::Regex,
10}
11
12impl Default for ArgumentLooksSensitive {
13    fn default() -> Self {
14        let regex = regex::Regex::new("(:?passw|secret$|token$)").unwrap();
15        Self { regex }
16    }
17}
18
19impl LintPass for ArgumentLooksSensitive {
20    fn name(&self) -> &str {
21        "ArgumentLooksSensitive"
22    }
23
24    fn description(&self) -> &str {
25        "Warns if argument name looks like sensitive, but argument is not typed with type Sensitive"
26    }
27}
28
29impl EarlyLintPass for ArgumentLooksSensitive {
30    fn check_argument(
31        &self,
32        arg: &crate::puppet_lang::argument::Argument<Range>,
33    ) -> Vec<super::lint::LintError> {
34        let lc_name = arg.name.to_lowercase();
35        if self.regex.is_match(&lc_name) {
36            match &arg.type_spec {
37                None => vec![LintError::new(
38                    Box::new(self.clone()),
39                    &format!("Assuming argument {:?} contains a secret value, it is not typed with 'Sensitive'", arg.name),
40                    &arg.extra,
41                )],
42                Some(t)
43                    if !matches!(
44                        t.data,
45                        crate::puppet_lang::typing::TypeSpecificationVariant::Sensitive(_)
46                    ) =>
47                {
48                    vec![LintError::new(
49                        Box::new(self.clone()),
50                        &format!("Assuming argument {:?} contains a secret value, it is not typed with 'Sensitive' type", arg.name),
51                        &arg.extra,
52                    )]
53                }
54                Some(_) => vec![],
55            }
56        } else {
57            vec![]
58        }
59    }
60}
61
62#[derive(Clone, Serialize, Deserialize)]
63pub struct SensitiveArgumentWithDefault;
64
65impl LintPass for SensitiveArgumentWithDefault {
66    fn name(&self) -> &str {
67        "SensitiveArgumentWithDefault"
68    }
69    fn description(&self) -> &str {
70        "Warns if argument typed with Sensitive contains default value"
71    }
72}
73
74impl EarlyLintPass for SensitiveArgumentWithDefault {
75    fn check_argument(
76        &self,
77        arg: &crate::puppet_lang::argument::Argument<Range>,
78    ) -> Vec<super::lint::LintError> {
79        if let Some(t) = &arg.type_spec {
80            if matches!(
81                t.data,
82                crate::puppet_lang::typing::TypeSpecificationVariant::Sensitive(_)
83            ) && arg.default.is_some()
84            {
85                return vec![LintError::new(
86                    Box::new(self.clone()),
87                    "Sensitive argument with default value",
88                    &arg.extra,
89                )];
90            }
91        }
92        vec![]
93    }
94}
95
96#[derive(Clone, Serialize, Deserialize)]
97pub struct ArgumentTyped;
98
99impl LintPass for ArgumentTyped {
100    fn name(&self) -> &str {
101        "ArgumentTyped"
102    }
103
104    fn description(&self) -> &str {
105        "Warns if argument is not typed"
106    }
107}
108
109impl EarlyLintPass for ArgumentTyped {
110    fn check_argument(
111        &self,
112        arg: &crate::puppet_lang::argument::Argument<Range>,
113    ) -> Vec<super::lint::LintError> {
114        if arg.type_spec.is_none() {
115            return vec![LintError::new(
116                Box::new(self.clone()),
117                "Argument is not typed",
118                &arg.extra,
119            )];
120        }
121        vec![]
122    }
123}
124
125#[derive(Clone, Serialize, Deserialize)]
126pub struct ReadableArgumentsName {
127    #[serde(with = "serde_regex")]
128    regex: regex::Regex,
129}
130
131impl Default for ReadableArgumentsName {
132    fn default() -> Self {
133        let regex = regex::Regex::new("^.$").unwrap();
134        Self { regex }
135    }
136}
137
138impl LintPass for ReadableArgumentsName {
139    fn name(&self) -> &str {
140        "ReadableArgumentsName"
141    }
142
143    fn description(&self) -> &str {
144        "Warns if argument name is not readable enough"
145    }
146}
147
148impl EarlyLintPass for ReadableArgumentsName {
149    fn check_argument(
150        &self,
151        arg: &crate::puppet_lang::argument::Argument<Range>,
152    ) -> Vec<super::lint::LintError> {
153        if self.regex.is_match(&arg.name) {
154            return vec![LintError::new(
155                Box::new(self.clone()),
156                &format!("Argument '{}' name is too short", arg.name),
157                &arg.extra,
158            )];
159        }
160        vec![]
161    }
162}
163
164#[derive(Clone, Serialize, Deserialize)]
165pub struct LowerCaseArgumentName;
166
167impl LintPass for LowerCaseArgumentName {
168    fn name(&self) -> &str {
169        "LowerCaseArgumentName"
170    }
171
172    fn description(&self) -> &str {
173        "Warns if argument name is not lowercase, as suggested by Puppet's style guide"
174    }
175}
176
177impl EarlyLintPass for LowerCaseArgumentName {
178    fn check_argument(
179        &self,
180        arg: &crate::puppet_lang::argument::Argument<Range>,
181    ) -> Vec<super::lint::LintError> {
182        if arg.name.chars().any(|c| c.is_uppercase()) {
183            return vec![LintError::new_with_url(
184                Box::new(self.clone()),
185                "Argument name with upper case letters.",
186                "https://puppet.com/docs/puppet/7/style_guide.html#style_guide_variables-variable-format",
187                &arg.extra,
188            )];
189        }
190        vec![]
191    }
192}