1#![warn(missing_docs)]
4
5use async_trait::async_trait;
6use nargo_ir::IRModule;
7use nargo_types::{Error, Result};
8use serde::{Deserialize, Serialize};
9
10pub mod rules;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub enum Severity {
15 Error,
17 Warning,
19 Hint,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct Diagnostic {
26 pub code: String,
28 pub message: String,
30 pub severity: Severity,
32 pub line: usize,
34 pub column: usize,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct FixAction {
41 pub start: usize,
43 pub end: usize,
45 pub replacement: String,
47 pub description: String,
49}
50
51#[async_trait]
53pub trait LintRule: Send + Sync {
54 fn name(&self) -> &'static str;
56 async fn check(&self, source: &str, module: &IRModule) -> Result<Vec<Diagnostic>>;
58 async fn fix(&self, source: &str, module: &IRModule) -> Result<Vec<FixAction>>;
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct RuleConfig {
65 pub enabled: bool,
67 pub severity: Option<Severity>,
69 pub fix: Option<bool>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct LinterConfig {
76 pub rules: std::collections::HashMap<String, RuleConfig>,
78 pub extends: Option<String>,
80}
81
82pub fn get_predefined_rule_set(name: &str) -> Option<LinterConfig> {
84 match name {
85 "recommended" => Some(LinterConfig { rules: vec![("no-console".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Warning), fix: None }), ("no-debugger".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Error), fix: None }), ("no-unused-vars".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Warning), fix: None }), ("no-unreachable-code".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Warning), fix: None }), ("no-magic-numbers".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Warning), fix: None }), ("no-alert".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Warning), fix: None }), ("no-undeclared-variables".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Error), fix: None })].into_iter().collect(), extends: None }),
86 "strict" => Some(LinterConfig {
87 rules: vec![("no-console".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Error), fix: None }), ("no-debugger".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Error), fix: None }), ("no-unused-vars".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Error), fix: None }), ("no-unreachable-code".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Error), fix: None }), ("no-empty-template".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Error), fix: None }), ("no-deprecated-tags".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Error), fix: None }), ("no-magic-numbers".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Error), fix: None }), ("no-alert".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Error), fix: None }), ("no-undeclared-variables".to_string(), RuleConfig { enabled: true, severity: Some(Severity::Error), fix: None })].into_iter().collect(),
88 extends: None,
89 }),
90 _ => None,
91 }
92}
93
94pub struct NargoLinter {
96 rules: Vec<Box<dyn LintRule>>,
97 config: LinterConfig,
98}
99
100impl NargoLinter {
101 pub fn new() -> Self {
103 let config = LinterConfig { rules: std::collections::HashMap::new(), extends: None };
104 Self { rules: Vec::new(), config }
105 }
106
107 pub fn with_config(mut config: LinterConfig) -> Self {
109 if let Some(extends) = &config.extends {
111 if let Some(rule_set) = get_predefined_rule_set(extends) {
112 for (rule_name, rule_config) in rule_set.rules {
114 if !config.rules.contains_key(&rule_name) {
115 config.rules.insert(rule_name, rule_config);
116 }
117 }
118 }
119 }
120 Self { rules: Vec::new(), config }
121 }
122
123 pub fn with_all_rules(config: LinterConfig) -> Self {
125 let mut linter = Self::with_config(config);
126 linter.add_builtin_rules();
127 linter
128 }
129
130 pub fn add_builtin_rules(&mut self) {
132 self.add_rule(Box::new(rules::NoConsole));
133 self.add_rule(Box::new(rules::NoDebugger));
134 self.add_rule(Box::new(rules::NoDeprecatedTags));
135 self.add_rule(Box::new(rules::NoEmptyTemplate));
136 self.add_rule(Box::new(rules::NoUnusedVars));
137 self.add_rule(Box::new(rules::NoUnreachableCode));
138 self.add_rule(Box::new(rules::NoMagicNumbers));
139 self.add_rule(Box::new(rules::NoAlert));
140 self.add_rule(Box::new(rules::NoUndeclaredVariables));
141 }
142
143 pub fn add_rule(&mut self, rule: Box<dyn LintRule>) {
145 self.rules.push(rule);
146 }
147
148 pub async fn run(&self, name: &str, source: &str) -> Result<Vec<Diagnostic>> {
150 let registry = std::sync::Arc::new(nargo_parser::ParserRegistry::new());
152 let mut parser = nargo_parser::Parser::new(name.to_string(), source, registry);
153 let module = parser.parse_all()?;
154 let mut all_diagnostics = Vec::new();
155
156 println!("Rules count: {}", self.rules.len());
157 println!("Module name: {}", module.name);
158 println!("Module has script: {:?}", module.script.is_some());
159 println!("Module has template: {:?}", module.template.is_some());
160 if let Some(script) = &module.script {
161 println!("Script body length: {}", script.body.len());
162 }
163 if let Some(template) = &module.template {
164 println!("Template nodes length: {}", template.nodes.len());
165 }
166
167 for rule in &self.rules {
168 let rule_name = rule.name();
169 println!("Running rule: {}", rule_name);
170
171 let is_enabled = if let Some(rule_config) = self.config.rules.get(rule_name) {
173 println!("Rule {} is enabled: {}", rule_name, rule_config.enabled);
174 rule_config.enabled
175 }
176 else {
177 println!("Rule {} is not in config, defaulting to enabled", rule_name);
179 true
180 };
181 if is_enabled {
182 let diagnostics = rule.check(source, &module).await?;
183 println!("Rule {} found {} diagnostics", rule_name, diagnostics.len());
184 all_diagnostics.extend(diagnostics);
185 }
186 }
187
188 println!("Total diagnostics: {}", all_diagnostics.len());
189 Ok(all_diagnostics)
190 }
191
192 pub fn config(&self) -> &LinterConfig {
194 &self.config
195 }
196
197 pub async fn run_fix(&self, name: &str, source: &str) -> Result<Vec<FixAction>> {
199 let registry = std::sync::Arc::new(nargo_parser::ParserRegistry::new());
201 let mut parser = nargo_parser::Parser::new(name.to_string(), source, registry);
202 let module = parser.parse_all()?;
203 let mut all_fix_actions = Vec::new();
204
205 for rule in &self.rules {
206 let rule_name = rule.name();
207
208 let is_enabled = if let Some(rule_config) = self.config.rules.get(rule_name) {
210 rule_config.enabled
211 }
212 else {
213 true
215 };
216
217 let is_fix_enabled = if let Some(rule_config) = self.config.rules.get(rule_name) {
219 rule_config.fix.unwrap_or(true)
220 }
221 else {
222 true
224 };
225
226 if is_enabled && is_fix_enabled {
227 let fix_actions = rule.fix(source, &module).await?;
228 all_fix_actions.extend(fix_actions);
229 }
230 }
231
232 Ok(all_fix_actions)
233 }
234
235 pub fn apply_fixes(&self, source: &str, fix_actions: &[FixAction]) -> String {
237 let mut sorted_actions = fix_actions.to_vec();
239 sorted_actions.sort_by(|a, b| b.start.cmp(&a.start));
240
241 let mut result = source.to_string();
242 for action in sorted_actions {
243 let start = action.start;
244 let end = action.end;
245 if start <= end && end <= result.len() {
246 result = format!("{}{}{}", &result[..start], action.replacement, &result[end..]);
247 }
248 }
249
250 result
251 }
252}