1#![allow(clippy::field_reassign_with_default)]
31
32mod file;
33mod rule;
34mod store;
35mod testing;
36
37pub mod autofix;
38pub mod directives;
39pub mod groups;
40pub mod rule_prelude;
41pub mod util;
42
43pub use self::{
44 file::File,
45 rule::{CstRule, Inferable, Outcome, Rule, RuleCtx, RuleLevel, RuleResult, Tag},
46 store::CstRuleStore,
47};
48pub use rslint_errors::{Diagnostic, Severity, Span};
49
50pub use crate::directives::{
51 apply_top_level_directives, skip_node, Directive, DirectiveError, DirectiveErrorKind,
52 DirectiveParser,
53};
54
55use dyn_clone::clone_box;
56use rslint_parser::{util::SyntaxNodeExt, SyntaxKind, SyntaxNode};
57use std::collections::HashMap;
58use std::sync::Arc;
59
60#[derive(Debug, Clone)]
63pub struct LintResult<'s> {
64 pub parser_diagnostics: Vec<Diagnostic>,
66 pub rule_results: HashMap<&'static str, RuleResult>,
68 pub directive_diagnostics: Vec<DirectiveError>,
70 pub store: &'s CstRuleStore,
71 pub parsed: SyntaxNode,
72 pub file_id: usize,
73 pub verbose: bool,
74 pub fixed_code: Option<String>,
75}
76
77impl LintResult<'_> {
78 pub fn diagnostics(&self) -> impl Iterator<Item = &Diagnostic> {
81 self.parser_diagnostics
82 .iter()
83 .chain(
84 self.rule_results
85 .values()
86 .map(|x| x.diagnostics.iter())
87 .flatten(),
88 )
89 .chain(self.directive_diagnostics.iter().map(|x| &x.diagnostic))
90 }
91
92 pub fn outcome(&self) -> Outcome {
94 self.diagnostics().into()
95 }
96
97 pub fn fix(&mut self, dirty: bool, file: &File) -> Option<String> {
101 if self
102 .parser_diagnostics
103 .iter()
104 .any(|x| x.severity == Severity::Error)
105 && !dirty
106 {
107 None
108 } else {
109 Some(autofix::recursively_apply_fixes(self, file))
110 }
111 }
112}
113
114pub fn lint_file<'s>(file: &File, store: &'s CstRuleStore, verbose: bool) -> LintResult<'s> {
116 let (diagnostics, node) = file.parse_with_errors();
117 lint_file_inner(node, diagnostics, file, store, verbose)
118}
119
120pub(crate) fn lint_file_inner<'s>(
122 node: SyntaxNode,
123 parser_diagnostics: Vec<Diagnostic>,
124 file: &File,
125 store: &'s CstRuleStore,
126 verbose: bool,
127) -> LintResult<'s> {
128 let mut new_store = store.clone();
129 let directives::DirectiveResult {
130 directives,
131 diagnostics: mut directive_diagnostics,
132 } = { DirectiveParser::new_with_store(node.clone(), file, store).get_file_directives() };
133
134 apply_top_level_directives(
135 directives.as_slice(),
136 &mut new_store,
137 &mut directive_diagnostics,
138 file.id,
139 );
140
141 let src: Arc<str> = Arc::from(node.to_string());
142
143 let results = new_store
145 .rules
146 .into_iter()
147 .map(|rule| {
148 (
149 rule.name(),
150 run_rule(
151 &*rule,
152 file.id,
153 node.clone(),
154 verbose,
155 &directives,
156 src.clone(),
157 ),
158 )
159 })
160 .collect();
161
162 LintResult {
163 parser_diagnostics,
164 rule_results: results,
165 directive_diagnostics,
166 store,
167 parsed: node,
168 file_id: file.id,
169 verbose,
170 fixed_code: None,
171 }
172}
173
174pub fn run_rule(
179 rule: &dyn CstRule,
180 file_id: usize,
181 root: SyntaxNode,
182 verbose: bool,
183 directives: &[Directive],
184 src: Arc<str>,
185) -> RuleResult {
186 assert!(root.kind() == SyntaxKind::SCRIPT || root.kind() == SyntaxKind::MODULE);
187 let mut ctx = RuleCtx {
188 file_id,
189 verbose,
190 diagnostics: vec![],
191 fixer: None,
192 src,
193 };
194
195 rule.check_root(&root, &mut ctx);
196
197 root.descendants_with_tokens_with(&mut |elem| {
198 match elem {
199 rslint_parser::NodeOrToken::Node(node) => {
200 if skip_node(directives, node, rule) || node.kind() == SyntaxKind::ERROR {
201 return false;
202 }
203 rule.check_node(node, &mut ctx);
204 }
205 rslint_parser::NodeOrToken::Token(tok) => {
206 let _ = rule.check_token(tok, &mut ctx);
207 }
208 };
209 true
210 });
211 RuleResult::new(ctx.diagnostics, ctx.fixer)
212}
213
214pub fn get_rule_by_name(name: &str) -> Option<Box<dyn CstRule>> {
216 CstRuleStore::new()
217 .builtins()
218 .rules
219 .iter()
220 .find(|rule| rule.name() == name)
221 .map(|rule| clone_box(&**rule))
222}
223
224pub fn get_group_rules_by_name(group_name: &str) -> Option<Vec<Box<dyn CstRule>>> {
227 use groups::*;
228
229 Some(match group_name {
230 "errors" => errors(),
231 "style" => style(),
232 "regex" => regex(),
233 _ => return None,
234 })
235}
236
237pub fn get_rule_suggestion(incorrect_rule_name: &str) -> Option<&str> {
239 let rules = CstRuleStore::new()
240 .builtins()
241 .rules
242 .into_iter()
243 .map(|rule| rule.name());
244 util::find_best_match_for_name(rules, incorrect_rule_name, None)
245}
246
247pub fn get_rule_docs(rule: &str) -> Option<&'static str> {
252 get_rule_by_name(rule).map(|rule| rule.docs())
253}
254
255macro_rules! trait_obj_helper {
256 ($($name:ident),* $(,)?) => {
257 vec![
258 $(
259 Box::new($name::default()) as Box<dyn Inferable>
260 ),*
261 ]
262 }
263}
264
265pub fn get_inferable_rules() -> Vec<Box<dyn Inferable>> {
268 use groups::style::*;
269
270 trait_obj_helper![BlockSpacing]
271}