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::{
rule::{CstRule, Outcome, Rule, RuleCtx, RuleLevel, RuleResult},
store::CstRuleStore,
};
pub use rslint_errors::{Diagnostic, Severity, Span};
use crate::directives::skip_node;
#[doc(inline)]
pub use crate::directives::{apply_top_level_directives, Directive, DirectiveParser};
use dyn_clone::clone_box;
use rayon::prelude::*;
use rslint_parser::{parse_module, parse_text, 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 store: &'s CstRuleStore,
pub rule_results: HashMap<&'static str, RuleResult>,
pub directive_diagnostics: Vec<Diagnostic>,
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())
}
pub fn outcome(&self) -> Outcome {
self.diagnostics().into()
}
pub fn fix(&mut self, dirty: bool) -> Option<String> {
if self
.parser_diagnostics
.iter()
.any(|x| x.severity == Severity::Error)
&& !dirty
{
None
} else {
Some(autofix::recursively_apply_fixes(self))
}
}
}
pub fn lint_file(
file_id: usize,
file_source: impl AsRef<str>,
module: bool,
store: &CstRuleStore,
verbose: bool,
) -> Result<LintResult, Diagnostic> {
let (parser_diagnostics, green) = if module {
let parse = parse_module(file_source.as_ref(), file_id);
(parse.errors().to_owned(), parse.green())
} else {
let parse = parse_text(file_source.as_ref(), file_id);
(parse.errors().to_owned(), parse.green())
};
lint_file_inner(
SyntaxNode::new_root(green),
parser_diagnostics,
file_id,
store,
verbose,
)
}
pub(crate) fn lint_file_inner(
node: SyntaxNode,
parser_diagnostics: Vec<Diagnostic>,
file_id: usize,
store: &CstRuleStore,
verbose: bool,
) -> Result<LintResult, Diagnostic> {
let mut new_store = store.clone();
let results = DirectiveParser::new(node.clone(), file_id, store).get_file_directives()?;
let mut directive_diagnostics = vec![];
let directives = results
.into_iter()
.map(|res| {
directive_diagnostics.extend(res.diagnostics);
res.directive
})
.collect::<Vec<_>>();
apply_top_level_directives(
directives.as_slice(),
&mut new_store,
&mut directive_diagnostics,
file_id,
);
let src = Arc::new(node.to_string());
let results = new_store
.rules
.par_iter()
.map(|rule| {
(
rule.name(),
run_rule(
&**rule,
file_id,
node.clone(),
verbose,
&directives,
src.clone(),
),
)
})
.collect();
Ok(LintResult {
parser_diagnostics,
store,
rule_results: results,
directive_diagnostics,
parsed: node,
file_id,
verbose,
fixed_code: None,
})
}
pub fn run_rule(
rule: &dyn CstRule,
file_id: usize,
root: SyntaxNode,
verbose: bool,
directives: &[Directive],
src: Arc<String>,
) -> 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(),
_ => 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())
}