use std::path::Path;
use crate::linter::diagnostic::{Diagnostic, Severity};
use crate::semantic::SemanticModel;
use crate::syntax::{SyntaxElement, SyntaxKind, SyntaxNode};
pub mod correctness;
pub mod suspicious;
pub struct Example {
pub caption: &'static str,
pub source: &'static str,
}
pub fn all_rules() -> Vec<Box<dyn Rule>> {
vec![
Box::new(correctness::UnusedBinding),
Box::new(correctness::UnusedImport),
Box::new(correctness::DuplicateArgument),
Box::new(suspicious::AssignmentInCondition),
]
}
pub fn all_rule_ids() -> Vec<&'static str> {
all_rules().iter().map(|r| r.id()).collect()
}
pub struct RuleContext<'a> {
pub path: Option<&'a Path>,
pub root: &'a SyntaxNode,
pub model: &'a SemanticModel,
}
pub trait Rule: Send + Sync {
fn id(&self) -> &'static str;
fn default_severity(&self) -> Severity {
Severity::Warning
}
fn default_enabled(&self) -> bool {
true
}
fn description(&self) -> &'static str {
""
}
fn examples(&self) -> &'static [Example] {
&[]
}
fn interests(&self) -> &'static [SyntaxKind] {
&[]
}
fn check(&self, el: &SyntaxElement, ctx: &RuleContext<'_>, sink: &mut Vec<Diagnostic>) {
let _ = (el, ctx, sink);
}
fn check_file(&self, ctx: &RuleContext<'_>, sink: &mut Vec<Diagnostic>) {
let _ = (ctx, sink);
}
}
pub struct ResolvedRules {
rules: Vec<Box<dyn Rule>>,
}
impl ResolvedRules {
pub fn resolve(select: Option<&[String]>, ignore: &[String]) -> Self {
let rules = all_rules()
.into_iter()
.filter(|rule| {
let enabled = match select {
Some(selected) => selected.iter().any(|id| id == rule.id()),
None => rule.default_enabled(),
};
enabled && !ignore.iter().any(|id| id == rule.id())
})
.collect();
Self { rules }
}
pub fn run(&self, ctx: &RuleContext<'_>) -> Vec<Diagnostic> {
run_rules(&self.rules, ctx)
}
pub fn is_empty(&self) -> bool {
self.rules.is_empty()
}
}
fn run_rules(rules: &[Box<dyn Rule>], ctx: &RuleContext<'_>) -> Vec<Diagnostic> {
let mut all = Vec::new();
let mut by_kind: Vec<Vec<usize>> = vec![Vec::new(); SyntaxKind::COUNT];
let mut any_node_rules = false;
for (i, rule) in rules.iter().enumerate() {
for kind in rule.interests() {
by_kind[*kind as usize].push(i);
any_node_rules = true;
}
}
if any_node_rules {
for el in ctx.root.descendants_with_tokens() {
for &i in &by_kind[el.kind() as usize] {
rules[i].check(&el, ctx, &mut all);
}
}
}
for rule in rules {
rule.check_file(ctx, &mut all);
}
if let Some(path) = ctx.path {
for diag in &mut all {
diag.path = Some(path.to_path_buf());
}
}
all.sort_by(|a, b| (a.start, a.end, &a.rule).cmp(&(b.start, b.end, &b.rule)));
all
}