use crate::checkstyle::api::ast::DetailAst;
use crate::checkstyle::api::check::Check;
use crate::checkstyle::api::config::Context;
use crate::checkstyle::api::error::CheckstyleResult;
use crate::checkstyle::api::event::SeverityLevel;
use crate::checkstyle::api::file::{FileContents, FileText};
use crate::checkstyle::api::violation::Violation;
use crate::checkstyle::parser::ast_impl::DetailAstImpl;
use crate::checkstyle::parser::java_parser::JavaParser;
use std::collections::{BTreeSet, HashMap};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum AstState {
Ordinary,
WithComments,
}
pub struct TreeWalker {
token_to_ordinary_checks: HashMap<i32, Vec<usize>>,
token_to_comment_checks: HashMap<i32, Vec<usize>>,
ordinary_checks: Vec<Arc<Mutex<dyn Check>>>,
comment_checks: Vec<Arc<Mutex<dyn Check>>>,
child_context: Context,
skip_file_on_java_parse_exception: bool,
java_parse_exception_severity: SeverityLevel,
}
impl TreeWalker {
pub fn new() -> Self {
Self {
token_to_ordinary_checks: HashMap::new(),
token_to_comment_checks: HashMap::new(),
ordinary_checks: Vec::new(),
comment_checks: Vec::new(),
child_context: Context::new(),
skip_file_on_java_parse_exception: false,
java_parse_exception_severity: SeverityLevel::Error,
}
}
pub fn add_check(&mut self, check: Arc<Mutex<dyn Check>>) -> CheckstyleResult<()> {
{
let mut check_guard = check.lock().unwrap();
check_guard.init()?;
let tokens = check_guard.get_default_tokens();
let requires_comments = check_guard.is_comment_nodes_required();
drop(check_guard);
if requires_comments {
let check_idx = self.comment_checks.len();
self.comment_checks.push(check.clone());
for token_id in tokens {
self.token_to_comment_checks
.entry(token_id)
.or_default()
.push(check_idx);
}
} else {
let check_idx = self.ordinary_checks.len();
self.ordinary_checks.push(check.clone());
for token_id in tokens {
self.token_to_ordinary_checks
.entry(token_id)
.or_default()
.push(check_idx);
}
}
}
Ok(())
}
pub fn process_file(
&self,
_file: &PathBuf,
file_text: &FileText,
) -> CheckstyleResult<BTreeSet<Violation>> {
let mut violations = BTreeSet::new();
if self.ordinary_checks.is_empty() && self.comment_checks.is_empty() {
return Ok(violations);
}
let file_contents = FileContents::new(file_text.clone());
let root_ast = match JavaParser::parse(&file_contents) {
Ok(ast) => ast,
Err(e) => {
if self.skip_file_on_java_parse_exception {
let violation = Violation::new(
1,
0,
0,
0,
self.java_parse_exception_severity,
"TreeWalker".to_string(),
"parse.exception".to_string(),
vec![e.to_string()],
"checkstyle".to_string(),
"TreeWalker".to_string(),
None,
);
violations.insert(violation);
return Ok(violations);
} else {
return Err(e);
}
}
};
if !self.ordinary_checks.is_empty() {
let file_violations = self.walk(&root_ast, &file_contents, AstState::Ordinary)?;
violations.extend(file_violations);
}
if !self.comment_checks.is_empty() {
let file_violations = self.walk(&root_ast, &file_contents, AstState::WithComments)?;
violations.extend(file_violations);
}
Ok(violations)
}
fn walk(
&self,
ast: &Arc<DetailAstImpl>,
contents: &FileContents,
ast_state: AstState,
) -> CheckstyleResult<BTreeSet<Violation>> {
let mut violations = BTreeSet::new();
self.notify_begin(ast, contents, ast_state)?;
self.process_iter(ast, ast_state)?;
let end_violations = self.notify_end(ast, ast_state)?;
violations.extend(end_violations);
Ok(violations)
}
fn notify_begin(
&self,
root_ast: &Arc<DetailAstImpl>,
_contents: &FileContents,
ast_state: AstState,
) -> CheckstyleResult<()> {
let checks = if ast_state == AstState::WithComments {
&self.comment_checks
} else {
&self.ordinary_checks
};
for check in checks.iter() {
let mut check_guard = check.lock().unwrap();
check_guard.clear_violations();
check_guard.begin_tree(root_ast.as_ref() as &dyn crate::checkstyle::api::ast::DetailAst)?;
}
Ok(())
}
fn notify_end(
&self,
root_ast: &Arc<DetailAstImpl>,
ast_state: AstState,
) -> CheckstyleResult<BTreeSet<Violation>> {
let mut violations = BTreeSet::new();
let checks = if ast_state == AstState::WithComments {
&self.comment_checks
} else {
&self.ordinary_checks
};
for check in checks.iter() {
let mut check_guard = check.lock().unwrap();
check_guard.finish_tree(root_ast.as_ref() as &dyn crate::checkstyle::api::ast::DetailAst)?;
let check_violations = check_guard.get_violations();
violations.extend(check_violations);
check_guard.clear_violations();
}
Ok(violations)
}
fn process_iter(&self, root: &Arc<DetailAstImpl>, ast_state: AstState) -> CheckstyleResult<()> {
let mut cur_node: Option<Arc<DetailAstImpl>> = Some(root.clone());
while let Some(node) = &cur_node {
self.notify_visit(node, ast_state)?;
let mut to_visit = node.get_first_child_arc();
while to_visit.is_none() {
if let Some(current) = &cur_node {
self.notify_leave(current, ast_state)?;
to_visit = current.get_next_sibling_arc();
cur_node = current.parent.clone();
} else {
break;
}
}
cur_node = to_visit;
}
Ok(())
}
fn notify_visit(&self, ast: &Arc<DetailAstImpl>, ast_state: AstState) -> CheckstyleResult<()> {
let token_id = ast.get_type();
let check_indices = self.get_list_of_check_indices(token_id, ast_state);
let checks = if ast_state == AstState::WithComments {
&self.comment_checks
} else {
&self.ordinary_checks
};
for &idx in &check_indices {
if let Some(check) = checks.get(idx) {
let mut check_guard = check.lock().unwrap();
check_guard.visit_token(ast.as_ref() as &dyn crate::checkstyle::api::ast::DetailAst)?;
}
}
Ok(())
}
fn notify_leave(&self, ast: &Arc<DetailAstImpl>, ast_state: AstState) -> CheckstyleResult<()> {
let token_id = ast.get_type();
let check_indices = self.get_list_of_check_indices(token_id, ast_state);
let checks = if ast_state == AstState::WithComments {
&self.comment_checks
} else {
&self.ordinary_checks
};
for &idx in &check_indices {
if let Some(check) = checks.get(idx) {
let mut check_guard = check.lock().unwrap();
check_guard.leave_token(ast.as_ref() as &dyn crate::checkstyle::api::ast::DetailAst)?;
}
}
Ok(())
}
fn get_list_of_check_indices(&self, token_id: i32, ast_state: AstState) -> Vec<usize> {
if ast_state == AstState::WithComments {
self.token_to_comment_checks
.get(&token_id)
.cloned()
.unwrap_or_default()
} else {
self.token_to_ordinary_checks
.get(&token_id)
.cloned()
.unwrap_or_default()
}
}
pub fn set_child_context(&mut self, context: Context) {
self.child_context = context;
}
}
impl Default for TreeWalker {
fn default() -> Self {
Self::new()
}
}