#![allow(clippy::field_reassign_with_default)]
mod file;
mod rule;
mod store;
mod testing;
pub mod autofix;
pub mod directives;
pub mod groups;
pub mod rule_prelude;
pub mod util;
pub use self::{
file::File,
rule::{CstRule, Inferable, Outcome, Rule, RuleCtx, RuleLevel, RuleResult, Tag},
store::CstRuleStore,
};
pub use rslint_errors::{Diagnostic, Severity, Span};
pub use crate::directives::{
apply_top_level_directives, skip_node, Directive, DirectiveError, DirectiveErrorKind,
DirectiveParser,
};
use dyn_clone::clone_box;
use rslint_parser::{util::SyntaxNodeExt, SyntaxKind, SyntaxNode};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct LintResult<'s> {
pub parser_diagnostics: Vec<Diagnostic>,
pub rule_results: HashMap<&'static str, RuleResult>,
pub directive_diagnostics: Vec<DirectiveError>,
pub store: &'s CstRuleStore,
pub parsed: SyntaxNode,
pub file_id: usize,
pub verbose: bool,
pub fixed_code: Option<String>,
}
impl LintResult<'_> {
pub fn diagnostics(&self) -> impl Iterator<Item = &Diagnostic> {
self.parser_diagnostics
.iter()
.chain(
self.rule_results
.values()
.map(|x| x.diagnostics.iter())
.flatten(),
)
.chain(self.directive_diagnostics.iter().map(|x| &x.diagnostic))
}
pub fn outcome(&self) -> Outcome {
self.diagnostics().into()
}
pub fn fix(&mut self, dirty: bool, file: &File) -> Option<String> {
if self
.parser_diagnostics
.iter()
.any(|x| x.severity == Severity::Error)
&& !dirty
{
None
} else {
Some(autofix::recursively_apply_fixes(self, file))
}
}
}
pub fn lint_file<'s>(file: &File, store: &'s CstRuleStore, verbose: bool) -> LintResult<'s> {
let (diagnostics, node) = file.parse_with_errors();
lint_file_inner(node, diagnostics, file, store, verbose)
}
pub(crate) fn lint_file_inner<'s>(
node: SyntaxNode,
parser_diagnostics: Vec<Diagnostic>,
file: &File,
store: &'s CstRuleStore,
verbose: bool,
) -> LintResult<'s> {
let mut new_store = store.clone();
let directives::DirectiveResult {
directives,
diagnostics: mut directive_diagnostics,
} = { DirectiveParser::new_with_store(node.clone(), file, store).get_file_directives() };
apply_top_level_directives(
directives.as_slice(),
&mut new_store,
&mut directive_diagnostics,
file.id,
);
let src: Arc<str> = Arc::from(node.to_string());
let results = new_store
.rules
.into_iter()
.map(|rule| {
(
rule.name(),
run_rule(
&*rule,
file.id,
node.clone(),
verbose,
&directives,
src.clone(),
),
)
})
.collect();
LintResult {
parser_diagnostics,
rule_results: results,
directive_diagnostics,
store,
parsed: node,
file_id: file.id,
verbose,
fixed_code: None,
}
}
pub fn run_rule(
rule: &dyn CstRule,
file_id: usize,
root: SyntaxNode,
verbose: bool,
directives: &[Directive],
src: Arc<str>,
) -> RuleResult {
assert!(root.kind() == SyntaxKind::SCRIPT || root.kind() == SyntaxKind::MODULE);
let mut ctx = RuleCtx {
file_id,
verbose,
diagnostics: vec![],
fixer: None,
src,
};
rule.check_root(&root, &mut ctx);
root.descendants_with_tokens_with(&mut |elem| {
match elem {
rslint_parser::NodeOrToken::Node(node) => {
if skip_node(directives, node, rule) || node.kind() == SyntaxKind::ERROR {
return false;
}
rule.check_node(node, &mut ctx);
}
rslint_parser::NodeOrToken::Token(tok) => {
let _ = rule.check_token(tok, &mut ctx);
}
};
true
});
RuleResult::new(ctx.diagnostics, ctx.fixer)
}
pub fn get_rule_by_name(name: &str) -> Option<Box<dyn CstRule>> {
CstRuleStore::new()
.builtins()
.rules
.iter()
.find(|rule| rule.name() == name)
.map(|rule| clone_box(&**rule))
}
pub fn get_group_rules_by_name(group_name: &str) -> Option<Vec<Box<dyn CstRule>>> {
use groups::*;
Some(match group_name {
"errors" => errors(),
"style" => style(),
"regex" => regex(),
_ => return None,
})
}
pub fn get_rule_suggestion(incorrect_rule_name: &str) -> Option<&str> {
let rules = CstRuleStore::new()
.builtins()
.rules
.into_iter()
.map(|rule| rule.name());
util::find_best_match_for_name(rules, incorrect_rule_name, None)
}
pub fn get_rule_docs(rule: &str) -> Option<&'static str> {
get_rule_by_name(rule).map(|rule| rule.docs())
}
macro_rules! trait_obj_helper {
($($name:ident),* $(,)?) => {
vec![
$(
Box::new($name::default()) as Box<dyn Inferable>
),*
]
}
}
pub fn get_inferable_rules() -> Vec<Box<dyn Inferable>> {
use groups::style::*;
trait_obj_helper![BlockSpacing]
}