#[allow(unused_imports)]
use crate::clang::ast::*;
#[allow(unused_imports)]
use crate::clang::*;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
use std::fmt;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct X86ClangTools {
pub format: X86ClangFormat,
pub tidy: X86ClangTidy,
pub include_fixer: X86IncludeFixer,
pub refactor: X86RefactoringTool,
pub query: X86ClangQuery,
pub source_files: Vec<PathBuf>,
pub working_dir: PathBuf,
pub compilation_db: Option<PathBuf>,
pub output_dir: Option<PathBuf>,
pub verbose: bool,
pub stats: X86ToolingStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86ToolingStats {
pub files_processed: usize,
pub format_changes: usize,
pub tidy_checks_run: usize,
pub tidy_diagnostics: usize,
pub includes_fixed: usize,
pub refactors_applied: usize,
pub queries_run: usize,
pub errors: usize,
pub warnings: usize,
pub total_time_ms: u64,
}
impl X86ToolingStats {
pub fn new() -> Self {
Self::default()
}
pub fn reset(&mut self) {
*self = Self::default();
}
pub fn summary(&self) -> String {
let mut s = String::new();
s.push_str("╔══════════════════════════════════════════╗\n");
s.push_str("║ X86 Clang Tooling Statistics ║\n");
s.push_str("╠══════════════════════════════════════════╣\n");
s.push_str(&format!(
"║ Files processed: {:>8} ║\n",
self.files_processed
));
s.push_str(&format!(
"║ Format changes: {:>8} ║\n",
self.format_changes
));
s.push_str(&format!(
"║ Tidy checks run: {:>8} ║\n",
self.tidy_checks_run
));
s.push_str(&format!(
"║ Tidy diagnostics: {:>8} ║\n",
self.tidy_diagnostics
));
s.push_str(&format!(
"║ Includes fixed: {:>8} ║\n",
self.includes_fixed
));
s.push_str(&format!(
"║ Refactors applied: {:>8} ║\n",
self.refactors_applied
));
s.push_str(&format!(
"║ Queries run: {:>8} ║\n",
self.queries_run
));
s.push_str(&format!(
"║ Errors: {:>8} ║\n",
self.errors
));
s.push_str(&format!(
"║ Warnings: {:>8} ║\n",
self.warnings
));
s.push_str(&format!(
"║ Total time: {:>5} ms ║\n",
self.total_time_ms
));
s.push_str("╚══════════════════════════════════════════╝\n");
s
}
}
impl X86ClangTools {
pub fn new() -> Self {
Self {
format: X86ClangFormat::default(),
tidy: X86ClangTidy::default(),
include_fixer: X86IncludeFixer::default(),
refactor: X86RefactoringTool::default(),
query: X86ClangQuery::default(),
source_files: Vec::new(),
working_dir: PathBuf::from("."),
compilation_db: None,
output_dir: None,
verbose: false,
stats: X86ToolingStats::new(),
}
}
pub fn with_working_dir<P: AsRef<Path>>(working_dir: P) -> Self {
let mut tools = Self::new();
tools.working_dir = working_dir.as_ref().to_path_buf();
tools
}
pub fn add_file<P: AsRef<Path>>(&mut self, path: P) {
self.source_files.push(path.as_ref().to_path_buf());
}
pub fn add_files<P: AsRef<Path>>(&mut self, paths: &[P]) {
for p in paths {
self.add_file(p);
}
}
pub fn set_compilation_db<P: AsRef<Path>>(&mut self, db_path: P) {
self.compilation_db = Some(db_path.as_ref().to_path_buf());
}
pub fn set_output_dir<P: AsRef<Path>>(&mut self, dir: P) {
self.output_dir = Some(dir.as_ref().to_path_buf());
}
pub fn set_verbose(&mut self, verbose: bool) {
self.verbose = verbose;
self.format.verbose = verbose;
self.tidy.verbose = verbose;
}
pub fn run_all(&mut self) -> X86ToolingResult {
let start = std::time::Instant::now();
let mut result = X86ToolingResult::new();
for file in self.source_files.clone() {
self.stats.files_processed += 1;
if self.format.enabled {
match self.format.format_file(&file) {
Ok(changes) => {
self.stats.format_changes += changes;
result.format_results.push(X86FormatResult {
file: file.clone(),
changes,
success: true,
});
}
Err(e) => {
self.stats.errors += 1;
result.format_results.push(X86FormatResult {
file: file.clone(),
changes: 0,
success: false,
});
if self.verbose {
eprintln!("Format error on {}: {}", file.display(), e);
}
}
}
}
if self.tidy.enabled {
match self.tidy.check_file(&file) {
Ok(diags) => {
let count = diags.len();
self.stats.tidy_checks_run += self.tidy.enabled_check_count();
self.stats.tidy_diagnostics += count;
result.tidy_results.push(X86TidyResult {
file: file.clone(),
diagnostics: diags,
success: true,
});
}
Err(e) => {
self.stats.errors += 1;
result.tidy_results.push(X86TidyResult {
file: file.clone(),
diagnostics: Vec::new(),
success: false,
});
if self.verbose {
eprintln!("Tidy error on {}: {}", file.display(), e);
}
}
}
}
if self.include_fixer.enabled {
match self.include_fixer.fix_file(&file) {
Ok(patches) => {
self.stats.includes_fixed += patches;
result.include_results.push(X86IncludeResult {
file: file.clone(),
patches,
success: true,
});
}
Err(e) => {
self.stats.errors += 1;
result.include_results.push(X86IncludeResult {
file: file.clone(),
patches: 0,
success: false,
});
if self.verbose {
eprintln!("Include fix error on {}: {}", file.display(), e);
}
}
}
}
if !self.refactor.operations.is_empty() {
match self.refactor.apply_all(&file) {
Ok(changes) => {
self.stats.refactors_applied += changes;
result.refactor_results.push(X86RefactorResult {
file: file.clone(),
changes,
success: true,
});
}
Err(e) => {
self.stats.errors += 1;
result.refactor_results.push(X86RefactorResult {
file: file.clone(),
changes: 0,
success: false,
});
if self.verbose {
eprintln!("Refactor error on {}: {}", file.display(), e);
}
}
}
}
if !self.query.matchers.is_empty() {
match self.query.query_file(&file) {
Ok(matches) => {
self.stats.queries_run += matches.len();
result.query_results.push(X86QueryResult {
file: file.clone(),
matches,
success: true,
});
}
Err(e) => {
self.stats.errors += 1;
result.query_results.push(X86QueryResult {
file: file.clone(),
matches: Vec::new(),
success: false,
});
if self.verbose {
eprintln!("Query error on {}: {}", file.display(), e);
}
}
}
}
}
self.stats.total_time_ms = start.elapsed().as_millis() as u64;
result.stats = self.stats.clone();
result
}
pub fn run_format(&mut self) -> Vec<X86FormatResult> {
self.tidy.enabled = false;
self.include_fixer.enabled = false;
self.refactor.operations.clear();
self.query.matchers.clear();
let result = self.run_all();
result.format_results
}
pub fn run_tidy(&mut self) -> Vec<X86TidyResult> {
self.format.enabled = false;
self.include_fixer.enabled = false;
self.refactor.operations.clear();
self.query.matchers.clear();
let result = self.run_all();
result.tidy_results
}
pub fn reset(&mut self) {
self.format = X86ClangFormat::default();
self.tidy = X86ClangTidy::default();
self.include_fixer = X86IncludeFixer::default();
self.refactor = X86RefactoringTool::default();
self.query = X86ClangQuery::default();
self.source_files.clear();
self.stats.reset();
}
}
impl Default for X86ClangTools {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct X86ToolingResult {
pub format_results: Vec<X86FormatResult>,
pub tidy_results: Vec<X86TidyResult>,
pub include_results: Vec<X86IncludeResult>,
pub refactor_results: Vec<X86RefactorResult>,
pub query_results: Vec<X86QueryResult>,
pub stats: X86ToolingStats,
}
impl X86ToolingResult {
pub fn new() -> Self {
Self {
format_results: Vec::new(),
tidy_results: Vec::new(),
include_results: Vec::new(),
refactor_results: Vec::new(),
query_results: Vec::new(),
stats: X86ToolingStats::new(),
}
}
pub fn is_success(&self) -> bool {
self.stats.errors == 0
}
pub fn total_changes(&self) -> usize {
self.stats.format_changes
+ self.stats.tidy_diagnostics
+ self.stats.includes_fixed
+ self.stats.refactors_applied
}
pub fn report(&self) -> String {
let mut s = String::new();
s.push_str(&self.stats.summary());
s.push_str("\n");
s.push_str(&format!("Format files: {}\n", self.format_results.len()));
s.push_str(&format!("Tidy files: {}\n", self.tidy_results.len()));
s.push_str(&format!("Include files:{}\n", self.include_results.len()));
s.push_str(&format!("Refactor files:{}\n", self.refactor_results.len()));
s.push_str(&format!("Query files: {}\n", self.query_results.len()));
s
}
}
impl Default for X86ToolingResult {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86FormatResult {
pub file: PathBuf,
pub changes: usize,
pub success: bool,
}
#[derive(Debug, Clone)]
pub struct X86TidyResult {
pub file: PathBuf,
pub diagnostics: Vec<X86TidyDiagnostic>,
pub success: bool,
}
#[derive(Debug, Clone)]
pub struct X86IncludeResult {
pub file: PathBuf,
pub patches: usize,
pub success: bool,
}
#[derive(Debug, Clone)]
pub struct X86RefactorResult {
pub file: PathBuf,
pub changes: usize,
pub success: bool,
}
#[derive(Debug, Clone)]
pub struct X86QueryResult {
pub file: PathBuf,
pub matches: Vec<X86QueryMatch>,
pub success: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86BraceStyle {
LLVM,
Google,
Chromium,
Mozilla,
WebKit,
Microsoft,
GNU,
}
impl fmt::Display for X86BraceStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::LLVM => write!(f, "LLVM"),
Self::Google => write!(f, "Google"),
Self::Chromium => write!(f, "Chromium"),
Self::Mozilla => write!(f, "Mozilla"),
Self::WebKit => write!(f, "WebKit"),
Self::Microsoft => write!(f, "Microsoft"),
Self::GNU => write!(f, "GNU"),
}
}
}
impl X86BraceStyle {
pub fn is_attached(&self) -> bool {
matches!(
self,
Self::LLVM | Self::Google | Self::Chromium | Self::Mozilla | Self::WebKit
)
}
pub fn function_brace_newline(&self) -> bool {
matches!(self, Self::GNU)
}
pub fn default_indent_width(&self) -> u8 {
match self {
Self::LLVM | Self::Google | Self::Chromium | Self::Mozilla | Self::Microsoft => 2,
Self::WebKit => 4,
Self::GNU => 2,
}
}
pub fn default_column_limit(&self) -> u16 {
match self {
Self::LLVM => 80,
Self::Google => 80,
Self::Chromium => 80,
Self::Mozilla => 99,
Self::WebKit => 120,
Self::Microsoft => 120,
Self::GNU => 79,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct X86IndentStyle {
pub use_tabs: bool,
pub width: u8,
pub indent_access_specifiers: bool,
pub indent_case_labels: bool,
pub indent_goto_labels: bool,
pub indent_pp_directives: bool,
pub indent_extern_block: bool,
pub namespace_indent: Option<u8>,
}
impl Default for X86IndentStyle {
fn default() -> Self {
Self {
use_tabs: false,
width: 2,
indent_access_specifiers: false,
indent_case_labels: false,
indent_goto_labels: false,
indent_pp_directives: false,
indent_extern_block: false,
namespace_indent: None,
}
}
}
impl X86IndentStyle {
pub fn spaces(width: u8) -> Self {
Self {
width,
..Default::default()
}
}
pub fn tabs(width: u8) -> Self {
Self {
use_tabs: true,
width,
..Default::default()
}
}
pub fn indent_string(&self, level: usize) -> String {
let total_spaces = level * self.width as usize;
if self.use_tabs {
"\t".repeat(level)
} else {
" ".repeat(total_spaces)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct X86LineBreakStyle {
pub column_limit: u16,
pub penalty_excess_character: u32,
pub penalty_break_before_first_call_parameter: u32,
pub penalty_break_comment: u32,
pub penalty_break_before_ternary: u32,
pub penalty_return_type_on_its_own_line: u32,
pub allow_short_functions_on_a_single_line: bool,
pub allow_short_if_statements_on_a_single_line: bool,
pub allow_short_loops_on_a_single_line: bool,
pub always_break_after_return_type: bool,
pub always_break_before_multiline_strings: bool,
pub always_break_template_declarations: bool,
pub break_constructor_initializers_before_colon: bool,
pub break_constructor_initializers_before_comma: bool,
pub break_after_return_type_in_declarations: bool,
}
impl Default for X86LineBreakStyle {
fn default() -> Self {
Self {
column_limit: 80,
penalty_excess_character: 1000000,
penalty_break_before_first_call_parameter: 100,
penalty_break_comment: 1000,
penalty_break_before_ternary: 100,
penalty_return_type_on_its_own_line: 60,
allow_short_functions_on_a_single_line: true,
allow_short_if_statements_on_a_single_line: false,
allow_short_loops_on_a_single_line: false,
always_break_after_return_type: false,
always_break_before_multiline_strings: false,
always_break_template_declarations: false,
break_constructor_initializers_before_colon: false,
break_constructor_initializers_before_comma: false,
break_after_return_type_in_declarations: false,
}
}
}
impl X86LineBreakStyle {
pub fn from_style(style: X86BraceStyle) -> Self {
let mut lb = Self::default();
lb.column_limit = style.default_column_limit();
match style {
X86BraceStyle::Mozilla => {
lb.column_limit = 99;
}
X86BraceStyle::WebKit | X86BraceStyle::Microsoft => {
lb.column_limit = 120;
}
X86BraceStyle::GNU => {
lb.column_limit = 79;
lb.always_break_after_return_type = true;
}
_ => {}
}
lb
}
pub fn exceeds_limit(&self, line: &str) -> bool {
if self.column_limit == 0 {
return false;
}
line.len() > self.column_limit as usize
}
pub fn find_break_point(&self, line: &str, column: usize) -> Option<usize> {
if column <= self.column_limit as usize {
return None;
}
let bytes = line.as_bytes();
let start = self.column_limit as usize;
for i in (0..start.min(bytes.len())).rev() {
match bytes[i] {
b',' | b';' | b' ' => return Some(i + 1),
b'(' | b'[' | b'{' => return Some(i + 1),
_ => {}
}
}
Some(self.column_limit as usize)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct X86WhitespaceStyle {
pub space_before_parens: bool,
pub spaces_in_parens: bool,
pub spaces_in_angles: bool,
pub spaces_in_square_brackets: bool,
pub spaces_around_binary_ops: bool,
pub spaces_around_assignment: bool,
pub spaces_before_trailing_comments: u8,
pub space_after_control_stmt_keyword: bool,
pub space_before_range_for_colon: bool,
pub space_after_range_for_colon: bool,
pub space_before_semicolon: bool,
pub space_before_cpp11_braced_list: bool,
pub space_before_block: bool,
pub space_before_single_line_block: bool,
pub max_empty_lines_to_keep: u8,
}
impl Default for X86WhitespaceStyle {
fn default() -> Self {
Self {
space_before_parens: false,
spaces_in_parens: false,
spaces_in_angles: false,
spaces_in_square_brackets: false,
spaces_around_binary_ops: true,
spaces_around_assignment: true,
spaces_before_trailing_comments: 2,
space_after_control_stmt_keyword: true,
space_before_range_for_colon: true,
space_after_range_for_colon: true,
space_before_semicolon: false,
space_before_cpp11_braced_list: true,
space_before_block: false,
space_before_single_line_block: false,
max_empty_lines_to_keep: 2,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct X86AlignmentStyle {
pub align_consecutive_assignments: bool,
pub align_consecutive_declarations: bool,
pub align_trailing_comments: bool,
pub align_escaped_newlines: bool,
pub align_operands: bool,
pub align_array_of_structures: bool,
pub trailing_comments_column: u16,
pub align_after_open_bracket: bool,
pub align_consecutive_macros: bool,
}
impl Default for X86AlignmentStyle {
fn default() -> Self {
Self {
align_consecutive_assignments: false,
align_consecutive_declarations: false,
align_trailing_comments: true,
align_escaped_newlines: true,
align_operands: true,
align_array_of_structures: false,
trailing_comments_column: 0,
align_after_open_bracket: false,
align_consecutive_macros: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86IncludeOrderStyle {
LLVM,
Google,
Alphabetical,
Custom,
None,
}
impl fmt::Display for X86IncludeOrderStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::LLVM => write!(f, "LLVM"),
Self::Google => write!(f, "Google"),
Self::Alphabetical => write!(f, "Alphabetical"),
Self::Custom => write!(f, "Custom"),
Self::None => write!(f, "None"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum X86IncludeCategory {
MainModuleHeader = 0,
LocalHeader = 1,
ProjectHeader = 2,
ThirdPartyHeader = 3,
CSystemHeader = 4,
CppSystemHeader = 5,
SystemHeader = 6,
UnknownHeader = 7,
}
impl fmt::Display for X86IncludeCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MainModuleHeader => write!(f, "MainModule"),
Self::LocalHeader => write!(f, "Local"),
Self::ProjectHeader => write!(f, "Project"),
Self::ThirdPartyHeader => write!(f, "ThirdParty"),
Self::CSystemHeader => write!(f, "CSystem"),
Self::CppSystemHeader => write!(f, "CppSystem"),
Self::SystemHeader => write!(f, "System"),
Self::UnknownHeader => write!(f, "Unknown"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86ClangFormat {
pub enabled: bool,
pub brace_style: X86BraceStyle,
pub indent: X86IndentStyle,
pub line_break: X86LineBreakStyle,
pub whitespace: X86WhitespaceStyle,
pub alignment: X86AlignmentStyle,
pub include_order: X86IncludeOrderStyle,
pub custom_include_order: Vec<String>,
pub reflow_comments: bool,
pub sort_using_declarations: bool,
pub sort_includes: bool,
pub fix_namespace_comments: bool,
pub derive_pointer_alignment: bool,
pub pointer_alignment: X86PointerAlignment,
pub verbose: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86PointerAlignment {
Left,
Right,
Middle,
}
impl fmt::Display for X86PointerAlignment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Left => write!(f, "Left (*ptr)"),
Self::Right => write!(f, "Right (ptr*)"),
Self::Middle => write!(f, "Middle (ptr * ptr)"),
}
}
}
impl Default for X86ClangFormat {
fn default() -> Self {
Self {
enabled: true,
brace_style: X86BraceStyle::LLVM,
indent: X86IndentStyle::default(),
line_break: X86LineBreakStyle::default(),
whitespace: X86WhitespaceStyle::default(),
alignment: X86AlignmentStyle::default(),
include_order: X86IncludeOrderStyle::LLVM,
custom_include_order: Vec::new(),
reflow_comments: true,
sort_using_declarations: true,
sort_includes: true,
fix_namespace_comments: true,
derive_pointer_alignment: false,
pointer_alignment: X86PointerAlignment::Right,
verbose: false,
}
}
}
impl X86ClangFormat {
pub fn with_style(style: X86BraceStyle) -> Self {
let mut fmt = Self::default();
fmt.set_style(style);
fmt
}
pub fn set_style(&mut self, style: X86BraceStyle) {
self.brace_style = style;
self.indent.width = style.default_indent_width();
self.line_break = X86LineBreakStyle::from_style(style);
match style {
X86BraceStyle::LLVM => {
self.indent.width = 2;
self.pointer_alignment = X86PointerAlignment::Right;
}
X86BraceStyle::Google => {
self.indent.width = 2;
self.include_order = X86IncludeOrderStyle::Google;
self.pointer_alignment = X86PointerAlignment::Left;
}
X86BraceStyle::Chromium => {
self.indent.width = 2;
self.pointer_alignment = X86PointerAlignment::Left;
}
X86BraceStyle::Mozilla => {
self.indent.width = 2;
self.line_break.column_limit = 99;
self.pointer_alignment = X86PointerAlignment::Left;
}
X86BraceStyle::WebKit => {
self.indent.width = 4;
self.line_break.column_limit = 120;
self.pointer_alignment = X86PointerAlignment::Left;
}
X86BraceStyle::Microsoft => {
self.line_break.column_limit = 120;
self.pointer_alignment = X86PointerAlignment::Left;
}
X86BraceStyle::GNU => {
self.indent.width = 2;
self.line_break.column_limit = 79;
self.line_break.always_break_after_return_type = true;
self.pointer_alignment = X86PointerAlignment::Right;
}
}
}
pub fn format_file<P: AsRef<Path>>(&self, path: P) -> Result<usize, String> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
let formatted = self.format_source(&content, path)?;
if formatted != content {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).map_err(|e| format!("Failed to create dir: {}", e))?;
}
fs::write(path, &formatted)
.map_err(|e| format!("Failed to write {}: {}", path.display(), e))?;
Ok(1)
} else {
Ok(0)
}
}
pub fn format_source(&self, source: &str, _path: &Path) -> Result<String, String> {
let mut result = String::with_capacity(source.len());
let lines: Vec<&str> = source.lines().collect();
let mut i = 0;
while i < lines.len() {
let line = lines[i];
if line.trim().is_empty() {
let mut blank_count = 0;
while i < lines.len() && lines[i].trim().is_empty() {
blank_count += 1;
i += 1;
}
let keep = blank_count.min(self.whitespace.max_empty_lines_to_keep as usize);
for _ in 0..keep {
result.push('\n');
}
continue;
}
let formatted = self.format_line(line);
result.push_str(&formatted);
if i + 1 < lines.len() {
result.push('\n');
}
i += 1;
}
if !result.ends_with('\n') {
result.push('\n');
}
Ok(result)
}
fn format_line(&self, line: &str) -> String {
let trimmed = line.trim();
if trimmed.is_empty() {
return String::new();
}
let mut result = trimmed.to_string();
result = result.trim_end().to_string();
result = self.coalesce_spaces(&result);
if self.derive_pointer_alignment {
result = self.fix_pointer_alignment(&result);
}
if self.brace_style.is_attached() && result.ends_with('{') && result.len() > 1 {
if !result.ends_with(" {") {
if let Some(pos) = result.rfind('{') {
if pos > 0 && result.as_bytes()[pos - 1] != b' ' {
let mut new = String::with_capacity(result.len() + 1);
new.push_str(&result[..pos]);
new.push(' ');
new.push_str(&result[pos..]);
result = new;
}
}
}
}
result
}
fn coalesce_spaces(&self, line: &str) -> String {
let mut result = String::with_capacity(line.len());
let bytes = line.as_bytes();
let mut in_string = false;
let mut in_char = false;
let mut prev_was_space = false;
let mut i = 0;
while i < bytes.len() {
let ch = bytes[i] as char;
if ch == '"' && !in_char {
if i == 0 || bytes[i - 1] != b'\\' {
in_string = !in_string;
}
}
if ch == '\'' && !in_string {
if i == 0 || bytes[i - 1] != b'\\' {
in_char = !in_char;
}
}
if in_string || in_char {
result.push(ch);
prev_was_space = false;
} else if ch == ' ' || ch == '\t' {
if !prev_was_space {
result.push(' ');
prev_was_space = true;
}
} else {
result.push(ch);
prev_was_space = false;
}
i += 1;
}
result
}
fn fix_pointer_alignment(&self, line: &str) -> String {
let mut result = line.to_string();
match self.pointer_alignment {
X86PointerAlignment::Left => {
result = result.replace(" * ", " *");
result = result.replace("* ", "* ");
}
X86PointerAlignment::Right => {
result = result.replace(" * ", " *");
}
X86PointerAlignment::Middle => {
result = result.replace("* ", " * ");
result = result.replace(" *", " * ");
result = result.replace(" * ", " * ");
}
}
result
}
pub fn format_block(&self, block: &str) -> Result<String, String> {
self.format_source(block, Path::new("<block>"))
}
pub fn classify_include(include_line: &str) -> X86IncludeCategory {
let trimmed = include_line.trim();
if !trimmed.starts_with("#include") {
return X86IncludeCategory::UnknownHeader;
}
let header = if trimmed.contains('<') && trimmed.contains('>') {
let start = trimmed.find('<').unwrap() + 1;
let end = trimmed.find('>').unwrap();
trimmed[start..end].to_string()
} else if trimmed.contains('"') {
let start = trimmed.find('"').unwrap() + 1;
let end = trimmed.rfind('"').unwrap();
trimmed[start..end].to_string()
} else {
return X86IncludeCategory::UnknownHeader;
};
let header_lower = header.to_lowercase();
let c_headers = [
"assert.h",
"complex.h",
"ctype.h",
"errno.h",
"fenv.h",
"float.h",
"inttypes.h",
"iso646.h",
"limits.h",
"locale.h",
"math.h",
"setjmp.h",
"signal.h",
"stdalign.h",
"stdarg.h",
"stdatomic.h",
"stdbool.h",
"stddef.h",
"stdint.h",
"stdio.h",
"stdlib.h",
"stdnoreturn.h",
"string.h",
"tgmath.h",
"threads.h",
"time.h",
"uchar.h",
"wchar.h",
"wctype.h",
];
if c_headers.contains(&header_lower.as_str()) {
return X86IncludeCategory::CSystemHeader;
}
let cpp_headers = [
"algorithm",
"array",
"atomic",
"bitset",
"chrono",
"codecvt",
"complex",
"condition_variable",
"deque",
"exception",
"filesystem",
"forward_list",
"fstream",
"functional",
"future",
"initializer_list",
"iomanip",
"ios",
"iosfwd",
"iostream",
"istream",
"iterator",
"limits",
"list",
"locale",
"map",
"memory",
"mutex",
"new",
"numeric",
"optional",
"ostream",
"queue",
"random",
"ratio",
"regex",
"scoped_allocator",
"set",
"shared_mutex",
"sstream",
"stack",
"stdexcept",
"streambuf",
"string",
"string_view",
"strstream",
"system_error",
"thread",
"tuple",
"type_traits",
"typeindex",
"typeinfo",
"unordered_map",
"unordered_set",
"utility",
"valarray",
"variant",
"vector",
];
for cpp_h in &cpp_headers {
if header_lower == *cpp_h || header_lower.starts_with(&format!("{}/", cpp_h)) {
return X86IncludeCategory::CppSystemHeader;
}
}
let sys_headers = [
"arpa/",
"net/",
"netinet/",
"sys/",
"unistd.h",
"fcntl.h",
"pthread.h",
"dlfcn.h",
"dirent.h",
"poll.h",
"sched.h",
"semaphore.h",
"termios.h",
"utime.h",
"wait.h",
];
for sys_h in &sys_headers {
if header_lower.starts_with(sys_h) || header_lower == *sys_h {
return X86IncludeCategory::SystemHeader;
}
}
if trimmed.contains('"') {
X86IncludeCategory::LocalHeader
} else {
X86IncludeCategory::SystemHeader
}
}
pub fn sort_includes(&self, includes: &[(String, X86IncludeCategory)]) -> Vec<String> {
match self.include_order {
X86IncludeOrderStyle::None => includes.iter().map(|(s, _)| s.clone()).collect(),
X86IncludeOrderStyle::Alphabetical => {
let mut sorted: Vec<_> = includes.to_vec();
sorted.sort_by(|a, b| {
let a_name = Self::extract_header_name(&a.0);
let b_name = Self::extract_header_name(&b.0);
a_name.to_lowercase().cmp(&b_name.to_lowercase())
});
sorted.into_iter().map(|(s, _)| s).collect()
}
X86IncludeOrderStyle::LLVM | X86IncludeOrderStyle::Google => {
let mut sorted: Vec<_> = includes.to_vec();
sorted.sort_by(|a, b| {
let cat_order = a.1.cmp(&b.1);
if cat_order != std::cmp::Ordering::Equal {
return cat_order;
}
let a_name = Self::extract_header_name(&a.0);
let b_name = Self::extract_header_name(&b.0);
a_name.to_lowercase().cmp(&b_name.to_lowercase())
});
sorted.into_iter().map(|(s, _)| s).collect()
}
X86IncludeOrderStyle::Custom => {
let mut sorted: Vec<_> = includes.to_vec();
sorted.sort_by(|a, b| {
let prio_a = self.custom_include_priority(&a.0);
let prio_b = self.custom_include_priority(&b.0);
prio_a.cmp(&prio_b).then_with(|| {
let a_name = Self::extract_header_name(&a.0);
let b_name = Self::extract_header_name(&b.0);
a_name.to_lowercase().cmp(&b_name.to_lowercase())
})
});
sorted.into_iter().map(|(s, _)| s).collect()
}
}
}
fn extract_header_name(include_line: &str) -> String {
let trimmed = include_line.trim();
if trimmed.contains('<') && trimmed.contains('>') {
let start = trimmed.find('<').unwrap() + 1;
let end = trimmed.find('>').unwrap();
trimmed[start..end].to_string()
} else if trimmed.contains('"') {
let start = trimmed.find('"').unwrap() + 1;
let end = trimmed.rfind('"').unwrap();
trimmed[start..end].to_string()
} else {
trimmed.to_string()
}
}
fn custom_include_priority(&self, include_line: &str) -> usize {
let name = Self::extract_header_name(include_line).to_lowercase();
for (i, pattern) in self.custom_include_order.iter().enumerate() {
if name.contains(&pattern.to_lowercase()) {
return i;
}
}
self.custom_include_order.len()
}
pub fn format_comment(&self, comment: &str) -> String {
let trimmed = comment.trim();
if trimmed.starts_with("//") || trimmed.starts_with("///") {
trimmed.to_string()
} else if trimmed.starts_with("/*") || trimmed.starts_with("/**") {
self.format_block_comment(trimmed)
} else {
trimmed.to_string()
}
}
fn format_block_comment(&self, comment: &str) -> String {
let mut result = String::with_capacity(comment.len());
let lines: Vec<&str> = comment.lines().collect();
for (i, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if i == 0 && trimmed.starts_with("/*") {
result.push_str(trimmed);
if lines.len() > 1 && !trimmed.ends_with("*/") {
result.push('\n');
}
} else if i == lines.len() - 1 && trimmed == "*/" {
result.push_str(" */");
} else {
let content = if trimmed.starts_with('*') {
let rest = trimmed[1..].trim();
if rest.is_empty() {
" *".to_string()
} else {
format!(" * {}", rest)
}
} else if !trimmed.is_empty() {
format!(" * {}", trimmed)
} else {
" *".to_string()
};
result.push_str(&content);
if i + 1 < lines.len() {
result.push('\n');
}
}
}
result
}
pub fn format_macro(&self, name: &str, body: &str) -> String {
let body_trimmed = body.trim();
if body_trimmed.is_empty() {
format!("#define {}", name)
} else if body_trimmed.contains('\n') || body_trimmed.len() > 60 {
let mut result = format!("#define {}", name);
let body_lines: Vec<&str> = body_trimmed.lines().collect();
for (i, line) in body_lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.is_empty() {
result.push_str(" \\\n");
} else {
result.push_str(&format!(" \\\n {}", trimmed));
}
if i == body_lines.len() - 1 {
}
}
result
} else {
format!("#define {} {}", name, body_trimmed)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum X86TidySeverity {
Note,
Warning,
Error,
Fatal,
}
impl fmt::Display for X86TidySeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Note => write!(f, "note"),
Self::Warning => write!(f, "warning"),
Self::Error => write!(f, "error"),
Self::Fatal => write!(f, "fatal"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86TidyDiagnostic {
pub severity: X86TidySeverity,
pub message: String,
pub check_name: String,
pub file: PathBuf,
pub line: usize,
pub column: usize,
pub fixit: Option<String>,
pub replacement_range: Option<(usize, usize, usize, usize)>,
pub notes: Vec<String>,
}
impl X86TidyDiagnostic {
pub fn new(
severity: X86TidySeverity,
message: impl Into<String>,
check_name: impl Into<String>,
file: PathBuf,
line: usize,
column: usize,
) -> Self {
Self {
severity,
message: message.into(),
check_name: check_name.into(),
file,
line,
column,
fixit: None,
replacement_range: None,
notes: Vec::new(),
}
}
pub fn with_fixit(mut self, fixit: impl Into<String>) -> Self {
self.fixit = Some(fixit.into());
self
}
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.notes.push(note.into());
self
}
pub fn format(&self) -> String {
format!(
"{}:{}:{}: {}: {} [{}]",
self.file.display(),
self.line,
self.column,
self.severity,
self.message,
self.check_name
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86TidyCategory {
Bugprone,
Modernize,
Performance,
Readability,
Security,
Portability,
Style,
Documentation,
}
impl fmt::Display for X86TidyCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Bugprone => write!(f, "bugprone"),
Self::Modernize => write!(f, "modernize"),
Self::Performance => write!(f, "performance"),
Self::Readability => write!(f, "readability"),
Self::Security => write!(f, "security"),
Self::Portability => write!(f, "portability"),
Self::Style => write!(f, "style"),
Self::Documentation => write!(f, "documentation"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86TidyCheckConfig {
pub name: String,
pub category: X86TidyCategory,
pub enabled: bool,
pub description: String,
pub options: HashMap<String, String>,
}
impl X86TidyCheckConfig {
pub fn new(
name: impl Into<String>,
category: X86TidyCategory,
description: impl Into<String>,
) -> Self {
Self {
name: name.into(),
category,
enabled: true,
description: description.into(),
options: HashMap::new(),
}
}
pub fn with_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.options.insert(key.into(), value.into());
self
}
}
#[derive(Debug, Clone)]
pub struct X86ClangTidy {
pub enabled: bool,
pub checks: Vec<X86TidyCheckConfig>,
pub suppress_patterns: Vec<String>,
pub warnings_as_errors: bool,
pub header_filter: Option<String>,
pub max_diagnostics_per_file: usize,
pub verbose: bool,
pub stats: X86TidyStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86TidyStats {
pub files_checked: usize,
pub lines_checked: usize,
pub checks_run: usize,
pub diagnostics_emitted: usize,
pub notes_emitted: usize,
pub warnings: usize,
pub errors: usize,
pub fixits_suggested: usize,
}
impl Default for X86ClangTidy {
fn default() -> Self {
let mut tidy = Self {
enabled: true,
checks: Vec::new(),
suppress_patterns: Vec::new(),
warnings_as_errors: false,
header_filter: None,
max_diagnostics_per_file: 0,
verbose: false,
stats: X86TidyStats::default(),
};
tidy.register_all_checks();
tidy
}
}
impl X86ClangTidy {
fn register_all_checks(&mut self) {
self.add_check(
"bugprone-assert-side-effect",
X86TidyCategory::Bugprone,
"Non-pure function calls in assert() statements",
);
self.add_check(
"bugprone-bad-signal-to-kill-thread",
X86TidyCategory::Bugprone,
"Using pthread_kill with signal that terminates threads",
);
self.add_check(
"bugprone-bool-pointer-implicit-conversion",
X86TidyCategory::Bugprone,
"Implicit conversion of bool* to other pointer types",
);
self.add_check(
"bugprone-copy-constructor-init",
X86TidyCategory::Bugprone,
"Copy constructors that don't initialize all members",
);
self.add_check(
"bugprone-dangling-handle",
X86TidyCategory::Bugprone,
"Handles that outlive the resources they refer to",
);
self.add_check(
"bugprone-exception-escape",
X86TidyCategory::Bugprone,
"Exceptions escaping from noexcept functions",
);
self.add_check(
"bugprone-fold-init-type",
X86TidyCategory::Bugprone,
"Fold expressions with unexpected init types",
);
self.add_check(
"bugprone-forward-declaration-namespace",
X86TidyCategory::Bugprone,
"Forward declarations in wrong namespaces",
);
self.add_check(
"bugprone-forwarding-reference-overload",
X86TidyCategory::Bugprone,
"Overloaded function templates with forwarding references",
);
self.add_check(
"bugprone-implicit-widening-of-multiplication-result",
X86TidyCategory::Bugprone,
"Integer multiplication results implicitly widened",
);
self.add_check(
"bugprone-inaccurate-erase",
X86TidyCategory::Bugprone,
"Erasing elements from associative containers incorrectly",
);
self.add_check(
"bugprone-incorrect-roundings",
X86TidyCategory::Bugprone,
"Incorrect rounding of floating-point values",
);
self.add_check(
"bugprone-infinite-loop",
X86TidyCategory::Bugprone,
"Loops that never terminate",
);
self.add_check(
"bugprone-integer-division",
X86TidyCategory::Bugprone,
"Integer division used in floating-point context",
);
self.add_check(
"bugprone-lambda-function-name",
X86TidyCategory::Bugprone,
"Using __func__ inside lambda returns enclosing function name",
);
self.add_check(
"bugprone-misaligned-member",
X86TidyCategory::Bugprone,
"Misaligned pointer members in structs",
);
self.add_check(
"bugprone-misplaced-operator-in-strlen-in-alloc",
X86TidyCategory::Bugprone,
"Misplaced operators inside strlen in malloc argument",
);
self.add_check(
"bugprone-misplaced-widening-cast",
X86TidyCategory::Bugprone,
"Widening casts placed incorrectly causing truncation",
);
self.add_check(
"bugprone-move-forwarding-reference",
X86TidyCategory::Bugprone,
"std::move on forwarding references",
);
self.add_check(
"bugprone-multiple-statement-macro",
X86TidyCategory::Bugprone,
"Macros with multiple statements not wrapped in do-while",
);
self.add_check(
"bugprone-no-escape",
X86TidyCategory::Bugprone,
"Functions that don't escape their pointer arguments",
);
self.add_check(
"bugprone-not-null-terminated-result",
X86TidyCategory::Bugprone,
"String functions that may not null-terminate their results",
);
self.add_check(
"bugprone-parent-virtual-call",
X86TidyCategory::Bugprone,
"Virtual calls made from constructors/destructors",
);
self.add_check(
"bugprone-posix-return",
X86TidyCategory::Bugprone,
"POSIX functions with errno return value checks",
);
self.add_check(
"bugprone-sizeof-array-argument",
X86TidyCategory::Bugprone,
"sizeof used on array function arguments",
);
self.add_check(
"bugprone-sizeof-expression",
X86TidyCategory::Bugprone,
"sizeof expressions producing unexpected results",
);
self.add_check(
"bugprone-string-constructor",
X86TidyCategory::Bugprone,
"String construction from single character",
);
self.add_check(
"bugprone-string-integer-assignment",
X86TidyCategory::Bugprone,
"Assigning integers to std::string",
);
self.add_check(
"bugprone-string-literal-with-embedded-nul",
X86TidyCategory::Bugprone,
"String literals containing embedded NUL characters",
);
self.add_check(
"bugprone-suspicious-enum-usage",
X86TidyCategory::Bugprone,
"Suspicious usage of enum values",
);
self.add_check(
"bugprone-suspicious-memset-usage",
X86TidyCategory::Bugprone,
"memset with zero length or suspicious value",
);
self.add_check(
"bugprone-suspicious-missing-comma",
X86TidyCategory::Bugprone,
"Suspicious missing comma in string literal arrays",
);
self.add_check(
"bugprone-suspicious-semicolon",
X86TidyCategory::Bugprone,
"Suspicious semicolons after if/for/while conditions",
);
self.add_check(
"bugprone-suspicious-string-compare",
X86TidyCategory::Bugprone,
"Suspicious string comparison operations",
);
self.add_check(
"bugprone-swapped-arguments",
X86TidyCategory::Bugprone,
"Swapped arguments in function calls",
);
self.add_check(
"bugprone-terminated-variadic",
X86TidyCategory::Bugprone,
"Variadic arguments not properly terminated",
);
self.add_check(
"bugprone-throw-keyword-missing",
X86TidyCategory::Bugprone,
"throw used where throw keyword is missing",
);
self.add_check(
"bugprone-too-small-loop-variable",
X86TidyCategory::Bugprone,
"Loop variable too small for the loop range",
);
self.add_check(
"bugprone-undefined-memory-manipulation",
X86TidyCategory::Bugprone,
"Manipulating memory of non-trivial types",
);
self.add_check(
"bugprone-undelegated-constructor",
X86TidyCategory::Bugprone,
"Constructors that don't delegate or initialize base",
);
self.add_check(
"bugprone-unhandled-exception-at-new",
X86TidyCategory::Bugprone,
"Exceptions not handled after operator new",
);
self.add_check(
"bugprone-unhandled-self-assignment",
X86TidyCategory::Bugprone,
"Self-assignment not handled in assignment operators",
);
self.add_check(
"bugprone-unsized-member-field",
X86TidyCategory::Bugprone,
"Unsized member fields in classes",
);
self.add_check(
"bugprone-unused-raii",
X86TidyCategory::Bugprone,
"RAII objects created but not assigned to a variable",
);
self.add_check(
"bugprone-unused-return-value",
X86TidyCategory::Bugprone,
"Return value of functions not used",
);
self.add_check(
"bugprone-use-after-move",
X86TidyCategory::Bugprone,
"Using a variable after it has been moved from",
);
self.add_check(
"bugprone-virtual-near-miss",
X86TidyCategory::Bugprone,
"Nearly matching virtual function signatures",
);
self.add_check(
"modernize-avoid-bind",
X86TidyCategory::Modernize,
"Replace std::bind with lambdas",
);
self.add_check(
"modernize-avoid-c-arrays",
X86TidyCategory::Modernize,
"Replace C arrays with std::array or std::vector",
);
self.add_check(
"modernize-deprecated-headers",
X86TidyCategory::Modernize,
"Replace deprecated C++ headers with modern alternatives",
);
self.add_check(
"modernize-loop-convert",
X86TidyCategory::Modernize,
"Convert traditional loops to range-based for loops",
);
self.add_check(
"modernize-make-shared",
X86TidyCategory::Modernize,
"Replace explicit new in std::shared_ptr with std::make_shared",
);
self.add_check(
"modernize-make-unique",
X86TidyCategory::Modernize,
"Replace explicit new in std::unique_ptr with std::make_unique",
);
self.add_check(
"modernize-pass-by-value",
X86TidyCategory::Modernize,
"Replace const-ref parameters that are copied with pass-by-value",
);
self.add_check(
"modernize-raw-string-literal",
X86TidyCategory::Modernize,
"Replace escaped strings with raw string literals",
);
self.add_check(
"modernize-redundant-void-arg",
X86TidyCategory::Modernize,
"Remove redundant void argument lists",
);
self.add_check(
"modernize-replace-auto-ptr",
X86TidyCategory::Modernize,
"Replace std::auto_ptr with std::unique_ptr",
);
self.add_check(
"modernize-replace-disallow-copy-and-assign-macro",
X86TidyCategory::Modernize,
"Replace DISALLOW_COPY_AND_ASSIGN macros with = delete",
);
self.add_check(
"modernize-replace-random-shuffle",
X86TidyCategory::Modernize,
"Replace std::random_shuffle with std::shuffle",
);
self.add_check(
"modernize-return-braced-init-list",
X86TidyCategory::Modernize,
"Use braced init list in return statements",
);
self.add_check(
"modernize-shrink-to-fit",
X86TidyCategory::Modernize,
"Replace swap trick with shrink_to_fit()",
);
self.add_check(
"modernize-unary-static-assert",
X86TidyCategory::Modernize,
"Replace static_assert with single-argument version",
);
self.add_check(
"modernize-use-auto",
X86TidyCategory::Modernize,
"Use auto when initializing with a cast or new",
);
self.add_check(
"modernize-use-bool-literals",
X86TidyCategory::Modernize,
"Replace integer literals with bool literals true/false",
);
self.add_check(
"modernize-use-default-member-init",
X86TidyCategory::Modernize,
"Use default member initializer instead of constructor init",
);
self.add_check(
"modernize-use-emplace",
X86TidyCategory::Modernize,
"Use emplace_back instead of push_back for temporaries",
);
self.add_check(
"modernize-use-equals-default",
X86TidyCategory::Modernize,
"Use = default for default constructors/destructors",
);
self.add_check(
"modernize-use-equals-delete",
X86TidyCategory::Modernize,
"Use = delete for deleted functions instead of private",
);
self.add_check(
"modernize-use-nodiscard",
X86TidyCategory::Modernize,
"Add [[nodiscard]] to functions whose return value shouldn't be ignored",
);
self.add_check(
"modernize-use-noexcept",
X86TidyCategory::Modernize,
"Add noexcept to functions that don't throw",
);
self.add_check(
"modernize-use-nullptr",
X86TidyCategory::Modernize,
"Replace NULL and 0 used as pointers with nullptr",
);
self.add_check(
"modernize-use-override",
X86TidyCategory::Modernize,
"Add override to virtual functions that override",
);
self.add_check(
"modernize-use-trailing-return-type",
X86TidyCategory::Modernize,
"Use trailing return type syntax for functions",
);
self.add_check(
"modernize-use-transparent-functors",
X86TidyCategory::Modernize,
"Use transparent functors instead of deprecated ones",
);
self.add_check(
"modernize-use-using",
X86TidyCategory::Modernize,
"Replace typedef with using alias declarations",
);
self.add_check(
"performance-faster-string-find",
X86TidyCategory::Performance,
"Use string::find with single character instead of string",
);
self.add_check(
"performance-for-range-copy",
X86TidyCategory::Performance,
"Range-based for loop makes expensive copies",
);
self.add_check(
"performance-implicit-conversion-in-loop",
X86TidyCategory::Performance,
"Implicit conversions inside loops",
);
self.add_check(
"performance-inefficient-algorithm",
X86TidyCategory::Performance,
"Inefficient algorithm usage on associative containers",
);
self.add_check(
"performance-inefficient-string-concatenation",
X86TidyCategory::Performance,
"Inefficient string concatenation in loops",
);
self.add_check(
"performance-inefficient-vector-operation",
X86TidyCategory::Performance,
"Inefficient vector operations",
);
self.add_check(
"performance-move-const-arg",
X86TidyCategory::Performance,
"std::move on const variable prevents move semantics",
);
self.add_check(
"performance-move-constructor-init",
X86TidyCategory::Performance,
"Move constructor not using std::move to initialize members",
);
self.add_check(
"performance-no-automatic-move",
X86TidyCategory::Performance,
"Returning local variable that could use automatic move",
);
self.add_check(
"performance-noexcept-move-constructor",
X86TidyCategory::Performance,
"Move constructors not marked noexcept",
);
self.add_check(
"performance-trivial-automove",
X86TidyCategory::Performance,
"Trivial types that could use automatic move",
);
self.add_check(
"performance-type-promotion-in-math-fn",
X86TidyCategory::Performance,
"Unnecessary type promotion in math function calls",
);
self.add_check(
"performance-unnecessary-copy-initialization",
X86TidyCategory::Performance,
"Unnecessary copy initialization of objects",
);
self.add_check(
"performance-unnecessary-value-param",
X86TidyCategory::Performance,
"Unnecessary value parameters that could be const-ref",
);
self.add_check(
"readability-avoid-const-params-in-decls",
X86TidyCategory::Readability,
"Top-level const in function parameter declarations",
);
self.add_check(
"readability-braces-around-statements",
X86TidyCategory::Readability,
"Single-statement bodies should have braces",
);
self.add_check(
"readability-const-return-type",
X86TidyCategory::Readability,
"Const-qualified return types on value types",
);
self.add_check(
"readability-container-contains",
X86TidyCategory::Readability,
"Use container.contains() instead of count() > 0",
);
self.add_check(
"readability-container-data-pointer",
X86TidyCategory::Readability,
"Use data() instead of &container[0] to get pointer",
);
self.add_check(
"readability-container-size-empty",
X86TidyCategory::Readability,
"Use empty() instead of size() == 0",
);
self.add_check(
"readability-convert-member-functions-to-static",
X86TidyCategory::Readability,
"Member functions that can be made static",
);
self.add_check(
"readability-delete-null-pointer",
X86TidyCategory::Readability,
"Redundant null check before delete",
);
self.add_check(
"readability-deleted-default",
X86TidyCategory::Readability,
"Functions declared = default after = delete",
);
self.add_check(
"readability-else-after-return",
X86TidyCategory::Readability,
"Unnecessary else after return in if statement",
);
self.add_check(
"readability-function-cognitive-complexity",
X86TidyCategory::Readability,
"Functions with high cognitive complexity",
);
self.add_check(
"readability-function-size",
X86TidyCategory::Readability,
"Functions that exceed a configurable size threshold",
);
self.add_check(
"readability-identifier-length",
X86TidyCategory::Readability,
"Identifiers that are too short or too long",
);
self.add_check(
"readability-identifier-naming",
X86TidyCategory::Readability,
"Identifiers that don't follow naming conventions",
);
self.add_check(
"readability-implicit-bool-conversion",
X86TidyCategory::Readability,
"Implicit conversions of non-bool types to bool",
);
self.add_check(
"readability-inconsistent-declaration-parameter-name",
X86TidyCategory::Readability,
"Inconsistent parameter names between declaration and definition",
);
self.add_check(
"readability-isolate-declaration",
X86TidyCategory::Readability,
"Multiple declarations on a single line",
);
self.add_check(
"readability-magic-numbers",
X86TidyCategory::Readability,
"Magic numbers used in code without explanation",
);
self.add_check(
"readability-make-member-function-const",
X86TidyCategory::Readability,
"Member functions that could be const-qualified",
);
self.add_check(
"readability-misordered-members",
X86TidyCategory::Readability,
"Class member declarations not in a logical order",
);
self.add_check(
"readability-named-parameter",
X86TidyCategory::Readability,
"Parameters with no name in function declarations",
);
self.add_check(
"readability-non-const-parameter",
X86TidyCategory::Readability,
"Parameters that could be const pointers/refs",
);
self.add_check(
"readability-qualified-auto",
X86TidyCategory::Readability,
"auto with const/volatile/pointer qualifiers",
);
self.add_check(
"readability-redundancy",
X86TidyCategory::Readability,
"Various redundant code patterns",
);
self.add_check(
"readability-redundant-access-specifiers",
X86TidyCategory::Readability,
"Redundant access specifiers in class definitions",
);
self.add_check(
"readability-redundant-control-flow",
X86TidyCategory::Readability,
"Redundant control flow statements",
);
self.add_check(
"readability-redundant-declaration",
X86TidyCategory::Readability,
"Redundant forward declarations",
);
self.add_check(
"readability-redundant-function-ptr-dereference",
X86TidyCategory::Readability,
"Redundant dereference of function pointers",
);
self.add_check(
"readability-redundant-member-init",
X86TidyCategory::Readability,
"Redundant member initializations",
);
self.add_check(
"readability-redundant-preprocessor",
X86TidyCategory::Readability,
"Redundant preprocessor directives",
);
self.add_check(
"readability-redundant-smartptr-get",
X86TidyCategory::Readability,
"Redundant calls to .get() on smart pointers",
);
self.add_check(
"readability-redundant-string-cstr",
X86TidyCategory::Readability,
"Redundant calls to .c_str() on strings",
);
self.add_check(
"readability-redundant-string-init",
X86TidyCategory::Readability,
"Redundant string initialization with empty string",
);
self.add_check(
"readability-simplify-boolean-expr",
X86TidyCategory::Readability,
"Boolean expressions that can be simplified",
);
self.add_check(
"readability-simplify-subscript-expr",
X86TidyCategory::Readability,
"Subscript expressions that can be simplified",
);
self.add_check(
"readability-static-accessed-through-instance",
X86TidyCategory::Readability,
"Static members accessed through an instance",
);
self.add_check(
"readability-static-definition-in-anonymous-namespace",
X86TidyCategory::Readability,
"Static definitions that should be in anonymous namespace",
);
self.add_check(
"readability-string-compare",
X86TidyCategory::Readability,
"String comparisons that can be simplified",
);
self.add_check(
"readability-uniqueptr-delete-release",
X86TidyCategory::Readability,
"Calling release() and delete on unique_ptr",
);
self.add_check(
"readability-uppercase-literal-suffix",
X86TidyCategory::Readability,
"Integer/float literal suffixes should be uppercase",
);
}
fn add_check(&mut self, name: &str, category: X86TidyCategory, description: &str) {
self.checks
.push(X86TidyCheckConfig::new(name, category, description));
}
pub fn enable_check(&mut self, name: &str) {
for check in &mut self.checks {
if check.name == name {
check.enabled = true;
return;
}
}
}
pub fn disable_check(&mut self, name: &str) {
for check in &mut self.checks {
if check.name == name {
check.enabled = false;
return;
}
}
}
pub fn is_check_enabled(&self, name: &str) -> bool {
self.checks.iter().any(|c| c.name == name && c.enabled)
}
pub fn enable_category(&mut self, category: X86TidyCategory) {
for check in &mut self.checks {
if check.category == category {
check.enabled = true;
}
}
}
pub fn disable_category(&mut self, category: X86TidyCategory) {
for check in &mut self.checks {
if check.category == category {
check.enabled = false;
}
}
}
pub fn enabled_check_count(&self) -> usize {
self.checks.iter().filter(|c| c.enabled).count()
}
pub fn enabled_check_names(&self) -> Vec<String> {
self.checks
.iter()
.filter(|c| c.enabled)
.map(|c| c.name.clone())
.collect()
}
pub fn check_file<P: AsRef<Path>>(&self, path: P) -> Result<Vec<X86TidyDiagnostic>, String> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
let mut diagnostics = Vec::new();
let lines: Vec<&str> = content.lines().collect();
for (line_idx, line) in lines.iter().enumerate() {
let line_num = line_idx + 1;
if self.is_suppressed(line) {
continue;
}
for check in &self.checks {
if !check.enabled {
continue;
}
let diags = match check.name.as_str() {
"bugprone-assert-side-effect" => {
self.check_assert_side_effect(line, line_num, path)
}
"bugprone-suspicious-semicolon" => {
self.check_suspicious_semicolon(line, line_num, path)
}
"bugprone-infinite-loop" => self.check_infinite_loop(line, line_num, path),
"bugprone-integer-division" => {
self.check_integer_division(line, line_num, path)
}
"bugprone-sizeof-expression" => {
self.check_sizeof_expression(line, line_num, path)
}
"bugprone-unused-return-value" => {
self.check_unused_return_value(line, line_num, path)
}
"bugprone-use-after-move" => self.check_use_after_move(line, line_num, path),
"bugprone-multiple-statement-macro" => {
self.check_multiple_statement_macro(line, line_num, path)
}
"bugprone-string-literal-with-embedded-nul" => {
self.check_embedded_nul(line, line_num, path)
}
"bugprone-suspicious-memset-usage" => {
self.check_suspicious_memset(line, line_num, path)
}
"bugprone-swapped-arguments" => {
self.check_swapped_arguments(line, line_num, path)
}
"bugprone-too-small-loop-variable" => {
self.check_too_small_loop_var(line, line_num, path)
}
"modernize-use-nullptr" => self.check_use_nullptr(line, line_num, path),
"modernize-use-override" => self.check_use_override(line, line_num, path),
"modernize-use-auto" => self.check_use_auto(line, line_num, path),
"modernize-loop-convert" => self.check_loop_convert(line, line_num, path),
"modernize-use-emplace" => self.check_use_emplace(line, line_num, path),
"modernize-use-equals-default" => {
self.check_use_equals_default(line, line_num, path)
}
"modernize-use-equals-delete" => {
self.check_use_equals_delete(line, line_num, path)
}
"modernize-use-nodiscard" => self.check_use_nodiscard(line, line_num, path),
"modernize-use-noexcept" => self.check_use_noexcept(line, line_num, path),
"modernize-use-using" => self.check_use_using(line, line_num, path),
"modernize-raw-string-literal" => {
self.check_raw_string_literal(line, line_num, path)
}
"modernize-pass-by-value" => self.check_pass_by_value(line, line_num, path),
"performance-for-range-copy" => self.check_for_range_copy(line, line_num, path),
"performance-inefficient-string-concatenation" => {
self.check_string_concat(line, line_num, path)
}
"performance-inefficient-vector-operation" => {
self.check_vector_operation(line, line_num, path)
}
"performance-move-const-arg" => self.check_move_const_arg(line, line_num, path),
"performance-noexcept-move-constructor" => {
self.check_noexcept_move(line, line_num, path)
}
"performance-unnecessary-copy-initialization" => {
self.check_unnecessary_copy(line, line_num, path)
}
"performance-unnecessary-value-param" => {
self.check_unnecessary_value_param(line, line_num, path)
}
"readability-braces-around-statements" => {
self.check_braces_around_stmts(line, line_num, path)
}
"readability-container-size-empty" => {
self.check_size_empty(line, line_num, path)
}
"readability-else-after-return" => {
self.check_else_after_return(line, line_num, path)
}
"readability-implicit-bool-conversion" => {
self.check_implicit_bool(line, line_num, path)
}
"readability-magic-numbers" => self.check_magic_numbers(line, line_num, path),
"readability-redundant-string-cstr" => {
self.check_redundant_cstr(line, line_num, path)
}
"readability-redundant-string-init" => {
self.check_redundant_string_init(line, line_num, path)
}
"readability-simplify-boolean-expr" => {
self.check_simplify_bool(line, line_num, path)
}
"readability-uppercase-literal-suffix" => {
self.check_literal_suffix(line, line_num, path)
}
"readability-isolate-declaration" => {
self.check_isolate_decl(line, line_num, path)
}
"readability-delete-null-pointer" => {
self.check_delete_null_ptr(line, line_num, path)
}
"readability-static-accessed-through-instance" => {
self.check_static_through_instance(line, line_num, path)
}
_ => Vec::new(),
};
diagnostics.extend(diags);
if self.max_diagnostics_per_file > 0
&& diagnostics.len() >= self.max_diagnostics_per_file
{
break;
}
}
}
Ok(diagnostics)
}
fn is_suppressed(&self, line: &str) -> bool {
for pattern in &self.suppress_patterns {
if line.contains(pattern.as_str()) {
return true;
}
}
false
}
fn check_assert_side_effect(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains("assert(")
&& (line.contains("++") || line.contains("--") || line.contains(" = "))
{
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Assert expression has side effects",
"bugprone-assert-side-effect",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_suspicious_semicolon(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
let trimmed = line.trim();
if (trimmed.starts_with("if (") || trimmed.starts_with("if(")) && trimmed.ends_with(");") {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Suspicious semicolon after if condition",
"bugprone-suspicious-semicolon",
file.to_path_buf(),
line_num,
1,
));
}
if (trimmed.starts_with("for (") || trimmed.starts_with("for(")) && trimmed.ends_with(");")
{
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Suspicious semicolon after for condition",
"bugprone-suspicious-semicolon",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_infinite_loop(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
let trimmed = line.trim();
if trimmed == "while (1)"
|| trimmed == "while (true)"
|| trimmed == "while(1)"
|| trimmed == "while(true)"
{
}
if trimmed == "for (;;)" || trimmed == "for(;;)" {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Note,
"Potentially infinite loop: for(;;)",
"bugprone-infinite-loop",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_integer_division(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if (line.contains("float ") || line.contains("double "))
&& line.contains(" / ")
&& !line.contains(".0")
&& !line.contains(".f")
{
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Integer division used in floating-point context",
"bugprone-integer-division",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_sizeof_expression(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains("sizeof") && line.contains("+") && !line.contains("sizeof(") {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"sizeof expression with addition may produce unexpected results",
"bugprone-sizeof-expression",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_unused_return_value(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
let trimmed = line.trim();
if trimmed.ends_with(");")
&& !trimmed.contains('=')
&& !trimmed.contains("return ")
&& (trimmed.contains("scanf(")
|| trimmed.contains("malloc(")
|| trimmed.contains("realloc("))
{
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Return value of function not checked",
"bugprone-unused-return-value",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_use_after_move(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
if line.contains("std::move(") {
let warning = X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Potential use-after-move detected",
"bugprone-use-after-move",
file.to_path_buf(),
line_num,
1,
);
return vec![warning];
}
Vec::new()
}
fn check_multiple_statement_macro(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.trim().starts_with("#define") && line.contains(';') && !line.contains("do {") {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Macro with multiple statements should use do-while(0)",
"bugprone-multiple-statement-macro",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_embedded_nul(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
if line.contains("\\0") && line.contains('"') {
vec![X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"String literal may contain embedded NUL",
"bugprone-string-literal-with-embedded-nul",
file.to_path_buf(),
line_num,
1,
)]
} else {
Vec::new()
}
}
fn check_suspicious_memset(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
if line.contains("memset(") && line.contains(", 0, 0)") {
vec![X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"memset with zero size has no effect",
"bugprone-suspicious-memset-usage",
file.to_path_buf(),
line_num,
1,
)]
} else {
Vec::new()
}
}
fn check_swapped_arguments(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
if line.contains("memset(") {
if line.contains("sizeof") {
let after_comma = line.find(',').map(|p| &line[p + 1..]).unwrap_or("");
if after_comma.trim().starts_with("sizeof") {
return vec![X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Possible swapped arguments in memset call",
"bugprone-swapped-arguments",
file.to_path_buf(),
line_num,
1,
)];
}
}
}
Vec::new()
}
fn check_too_small_loop_var(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains("int ") && line.contains(" < ") && line.contains(".size()") {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Loop variable type may be too small for loop bounds",
"bugprone-too-small-loop-variable",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_use_nullptr(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains("NULL") {
diags.push(
X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Use nullptr instead of NULL",
"modernize-use-nullptr",
file.to_path_buf(),
line_num,
1,
)
.with_fixit("nullptr"),
);
}
if line.contains(" = 0")
&& (line.contains('*') || line.contains("ptr") || line.contains("pointer"))
{
if !line.contains("int ") && !line.contains("= 0x") {
diags.push(
X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Use nullptr instead of 0 as null pointer constant",
"modernize-use-nullptr",
file.to_path_buf(),
line_num,
1,
)
.with_fixit("nullptr"),
);
}
}
diags
}
fn check_use_override(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
let trimmed = line.trim();
if trimmed.contains("virtual") && trimmed.ends_with(';') && !trimmed.contains("override") {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Use 'override' on virtual function overrides",
"modernize-use-override",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_use_auto(&self, line: &str, line_num: usize, file: &Path) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains("std::unique_ptr<") && line.contains(" = std::make_unique<") {
diags.push(
X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Use auto when initializing with make_unique",
"modernize-use-auto",
file.to_path_buf(),
line_num,
1,
)
.with_fixit("auto"),
);
}
if line.contains("std::shared_ptr<") && line.contains(" = std::make_shared<") {
diags.push(
X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Use auto when initializing with make_shared",
"modernize-use-auto",
file.to_path_buf(),
line_num,
1,
)
.with_fixit("auto"),
);
}
diags
}
fn check_loop_convert(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains(".begin()") && line.contains(".end()") && line.contains("++") {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Loop can be converted to range-based for loop",
"modernize-loop-convert",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_use_emplace(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains(".push_back(") && !line.contains("std::move") && line.contains('(') {
if line.matches('(').count() > line.matches(')').count() {
}
}
diags
}
fn check_use_equals_default(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
let trimmed = line.trim();
if trimmed == "{}" || (trimmed.ends_with("{}") && trimmed.len() < 15) {
diags.push(
X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Use = default instead of empty constructor/destructor body",
"modernize-use-equals-default",
file.to_path_buf(),
line_num,
1,
)
.with_fixit("= default;"),
);
}
diags
}
fn check_use_equals_delete(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.trim() == "private:" {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Note,
"Consider using = delete instead of private inaccessible functions",
"modernize-use-equals-delete",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_use_nodiscard(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
let trimmed = line.trim();
if (trimmed.contains("int ") || trimmed.contains("bool "))
&& trimmed.contains('(')
&& trimmed.ends_with(';')
&& !trimmed.contains("void ")
{
}
diags
}
fn check_use_noexcept(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
let trimmed = line.trim();
if trimmed.contains("swap(") && trimmed.ends_with(';') && !trimmed.contains("noexcept") {
diags.push(
X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Swap function should be marked noexcept",
"modernize-use-noexcept",
file.to_path_buf(),
line_num,
1,
)
.with_fixit("noexcept"),
);
}
diags
}
fn check_use_using(&self, line: &str, line_num: usize, file: &Path) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.trim().starts_with("typedef ") {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Use 'using' instead of 'typedef'",
"modernize-use-using",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_raw_string_literal(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains("\\\"") && line.contains('"')
|| line.contains("\\\\") && line.contains('"')
{
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Note,
"Consider using a raw string literal R\"(...)\"",
"modernize-raw-string-literal",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_pass_by_value(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains("const std::string& ") && line.contains("std::move") {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Pass by value and use std::move instead of const ref + copy",
"modernize-pass-by-value",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_for_range_copy(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains("for (")
&& line.contains(" : ")
&& !line.contains("auto&")
&& !line.contains("const auto&")
{
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Range-based for loop makes expensive copies; use const auto&",
"performance-for-range-copy",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_string_concat(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains("+=") && line.contains("std::string") {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Note,
"String concatenation in loop; consider ostringstream or reserve()",
"performance-inefficient-string-concatenation",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_vector_operation(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains("std::vector") && line.contains(".push_back(") {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Note,
"Consider reserve() before repeated push_back",
"performance-inefficient-vector-operation",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_move_const_arg(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
if line.contains("std::move(") && (line.contains("const ") || line.contains("const&")) {
vec![X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"std::move on const variable prevents move semantics",
"performance-move-const-arg",
file.to_path_buf(),
line_num,
1,
)]
} else {
Vec::new()
}
}
fn check_noexcept_move(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
let trimmed = line.trim();
if trimmed.contains("&&") && trimmed.ends_with(';') && !trimmed.contains("noexcept") {
diags.push(
X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Move constructor should be marked noexcept",
"performance-noexcept-move-constructor",
file.to_path_buf(),
line_num,
1,
)
.with_fixit("noexcept"),
);
}
diags
}
fn check_unnecessary_copy(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains("auto ")
&& line.contains(" = ")
&& !line.contains("&")
&& !line.contains("&&")
{
if line.contains("get(") || line.contains("find(") {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Unnecessary copy initialization; use auto& or const auto&",
"performance-unnecessary-copy-initialization",
file.to_path_buf(),
line_num,
1,
));
}
}
diags
}
fn check_unnecessary_value_param(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains("std::string ")
&& line.contains(',')
&& !line.contains('&')
&& !line.contains("std::string&")
{
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Parameter passed by value; consider const reference",
"performance-unnecessary-value-param",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_braces_around_stmts(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
let trimmed = line.trim();
if (trimmed.starts_with("if (") || trimmed.starts_with("if("))
&& trimmed.ends_with(')')
&& !trimmed.contains('{')
{
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Note,
"Statement should be enclosed in braces",
"readability-braces-around-statements",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_size_empty(&self, line: &str, line_num: usize, file: &Path) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains(".size()") && line.contains("== 0") {
diags.push(
X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Use .empty() instead of .size() == 0",
"readability-container-size-empty",
file.to_path_buf(),
line_num,
1,
)
.with_fixit(".empty()"),
);
}
if line.contains(".size()") && line.contains("> 0") {
diags.push(
X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Use !.empty() instead of .size() > 0",
"readability-container-size-empty",
file.to_path_buf(),
line_num,
1,
)
.with_fixit("!.empty()"),
);
}
diags
}
fn check_else_after_return(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
if line.trim() == "else" {
vec![X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Unnecessary else after return",
"readability-else-after-return",
file.to_path_buf(),
line_num,
1,
)]
} else {
Vec::new()
}
}
fn check_implicit_bool(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains("if (") {
let after_if = line.split("if (").nth(1).unwrap_or("");
if let Some(inner) = after_if.split(')').next() {
let inner = inner.trim();
if inner
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == ':')
&& !inner.is_empty()
&& inner != "true"
&& inner != "false"
{
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Note,
format!("Implicit bool conversion of '{}'", inner),
"readability-implicit-bool-conversion",
file.to_path_buf(),
line_num,
1,
));
}
}
}
diags
}
fn check_magic_numbers(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
let words: Vec<&str> = line.split(|c: char| !c.is_alphanumeric()).collect();
for word in &words {
if let Ok(n) = word.parse::<i64>() {
if ![0, 1, -1, 2].contains(&n) && n.abs() > 1 {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
format!("Magic number '{}' found; consider a named constant", n),
"readability-magic-numbers",
file.to_path_buf(),
line_num,
1,
));
break; }
}
}
diags
}
fn check_redundant_cstr(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
if line.contains(".c_str()") && !line.contains("printf") && !line.contains("fprintf") {
vec![X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Redundant call to .c_str()",
"readability-redundant-string-cstr",
file.to_path_buf(),
line_num,
1,
)]
} else {
Vec::new()
}
}
fn check_redundant_string_init(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
if line.contains("std::string ") && (line.contains("= \"\"") || line.contains("= \"\";")) {
vec![X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Redundant string initialization with empty string",
"readability-redundant-string-init",
file.to_path_buf(),
line_num,
1,
)]
} else {
Vec::new()
}
}
fn check_simplify_bool(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains(" == true") {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Simplify boolean expression: remove '== true'",
"readability-simplify-boolean-expr",
file.to_path_buf(),
line_num,
1,
));
}
if line.contains(" == false") {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Simplify boolean expression: use '!' instead of '== false'",
"readability-simplify-boolean-expr",
file.to_path_buf(),
line_num,
1,
));
}
if line.contains(" ? true : false") {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Simplify ternary operator: remove '? true : false'",
"readability-simplify-boolean-expr",
file.to_path_buf(),
line_num,
1,
));
}
diags
}
fn check_literal_suffix(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
let bytes = line.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i].is_ascii_digit() {
let start = i;
while i < bytes.len() && (bytes[i].is_ascii_digit() || bytes[i] == b'.') {
i += 1;
}
if i < bytes.len() && bytes[i] == b'l' {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Use uppercase 'L' instead of lowercase 'l' for long literal suffix",
"readability-uppercase-literal-suffix",
file.to_path_buf(),
line_num,
(start + 1) as usize,
));
break;
}
} else {
i += 1;
}
}
diags
}
fn check_isolate_decl(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
let trimmed = line.trim();
if trimmed.contains(',')
&& (trimmed.starts_with("int ")
|| trimmed.starts_with("float ")
|| trimmed.starts_with("double ")
|| trimmed.starts_with("char ")
|| trimmed.starts_with("auto "))
{
if trimmed.contains('*') || line.matches('*').count() > 1 {
diags.push(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"Multiple declarations on single line; isolate each declaration",
"readability-isolate-declaration",
file.to_path_buf(),
line_num,
1,
));
}
}
diags
}
fn check_delete_null_ptr(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
if line.contains("if (") && line.contains("!= nullptr") && line.contains("delete ") {
vec![X86TidyDiagnostic::new(
X86TidySeverity::Note,
"Redundant null check before delete; delete on nullptr is safe",
"readability-delete-null-pointer",
file.to_path_buf(),
line_num,
1,
)]
} else {
Vec::new()
}
}
fn check_static_through_instance(
&self,
line: &str,
line_num: usize,
file: &Path,
) -> Vec<X86TidyDiagnostic> {
let mut diags = Vec::new();
if line.contains('.') && !line.contains("->") && !line.contains("this.") {
}
diags
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct X86Include {
pub text: String,
pub header: String,
pub is_system: bool,
pub line: usize,
pub is_used: bool,
pub category: X86IncludeCategory,
}
impl X86Include {
pub fn parse(line: &str, line_num: usize) -> Option<Self> {
let trimmed = line.trim();
if !trimmed.starts_with("#include") {
return None;
}
let is_system = trimmed.contains('<') && trimmed.contains('>');
let header = if is_system {
let start = trimmed.find('<')? + 1;
let end = trimmed.find('>')?;
trimmed[start..end].to_string()
} else if trimmed.contains('"') {
let start = trimmed.find('"')? + 1;
let end = trimmed.rfind('"')?;
trimmed[start..end].to_string()
} else {
return None;
};
Some(Self {
text: trimmed.to_string(),
header,
is_system,
line: line_num,
is_used: true, category: X86ClangFormat::classify_include(trimmed),
})
}
}
#[derive(Debug, Clone)]
pub struct X86IncludeFixer {
pub enabled: bool,
pub symbol_map: HashMap<String, Vec<String>>,
pub c_headers: Vec<String>,
pub cpp_headers: Vec<String>,
pub remove_unused: bool,
pub sort_includes: bool,
pub order_style: X86IncludeOrderStyle,
pub add_missing: bool,
pub iwyu_map: HashMap<String, Vec<String>>,
pub iwyu_keep_pragma: bool,
}
impl Default for X86IncludeFixer {
fn default() -> Self {
let mut fixer = Self {
enabled: true,
symbol_map: HashMap::new(),
c_headers: Vec::new(),
cpp_headers: Vec::new(),
remove_unused: true,
sort_includes: true,
order_style: X86IncludeOrderStyle::LLVM,
add_missing: true,
iwyu_map: HashMap::new(),
iwyu_keep_pragma: false,
};
fixer.init_standard_headers();
fixer.init_symbol_map();
fixer
}
}
impl X86IncludeFixer {
fn init_standard_headers(&mut self) {
self.c_headers = vec![
"assert.h",
"complex.h",
"ctype.h",
"errno.h",
"fenv.h",
"float.h",
"inttypes.h",
"iso646.h",
"limits.h",
"locale.h",
"math.h",
"setjmp.h",
"signal.h",
"stdalign.h",
"stdarg.h",
"stdatomic.h",
"stdbool.h",
"stddef.h",
"stdint.h",
"stdio.h",
"stdlib.h",
"stdnoreturn.h",
"string.h",
"tgmath.h",
"threads.h",
"time.h",
"uchar.h",
"wchar.h",
"wctype.h",
]
.into_iter()
.map(|s| s.to_string())
.collect();
self.cpp_headers = vec![
"algorithm",
"array",
"atomic",
"chrono",
"deque",
"filesystem",
"forward_list",
"functional",
"future",
"iostream",
"iterator",
"list",
"map",
"memory",
"mutex",
"numeric",
"optional",
"queue",
"random",
"regex",
"set",
"sstream",
"stack",
"string",
"string_view",
"system_error",
"thread",
"tuple",
"type_traits",
"unordered_map",
"unordered_set",
"utility",
"variant",
"vector",
"span",
"format",
"ranges",
]
.into_iter()
.map(|s| s.to_string())
.collect();
}
fn init_symbol_map(&mut self) {
let mappings: Vec<(&str, &[&str])> = vec![
("printf", &["stdio.h", "cstdio"]),
("scanf", &["stdio.h", "cstdio"]),
("malloc", &["stdlib.h", "cstdlib"]),
("free", &["stdlib.h", "cstdlib"]),
("realloc", &["stdlib.h", "cstdlib"]),
("calloc", &["stdlib.h", "cstdlib"]),
("memcpy", &["string.h", "cstring"]),
("memset", &["string.h", "cstring"]),
("strlen", &["string.h", "cstring"]),
("strcmp", &["string.h", "cstring"]),
("strcpy", &["string.h", "cstring"]),
("strcat", &["string.h", "cstring"]),
("assert", &["assert.h", "cassert"]),
("abort", &["stdlib.h", "cstdlib"]),
("exit", &["stdlib.h", "cstdlib"]),
("atoi", &["stdlib.h", "cstdlib"]),
("atof", &["stdlib.h", "cstdlib"]),
("rand", &["stdlib.h", "cstdlib"]),
("qsort", &["stdlib.h", "cstdlib"]),
("fopen", &["stdio.h", "cstdio"]),
("fclose", &["stdio.h", "cstdio"]),
("fread", &["stdio.h", "cstdio"]),
("fwrite", &["stdio.h", "cstdio"]),
("fprintf", &["stdio.h", "cstdio"]),
("snprintf", &["stdio.h", "cstdio"]),
("std::vector", &["vector"]),
("std::string", &["string"]),
("std::map", &["map"]),
("std::set", &["set"]),
("std::unordered_map", &["unordered_map"]),
("std::unordered_set", &["unordered_set"]),
("std::shared_ptr", &["memory"]),
("std::unique_ptr", &["memory"]),
("std::make_shared", &["memory"]),
("std::make_unique", &["memory"]),
("std::move", &["utility"]),
("std::forward", &["utility"]),
("std::pair", &["utility"]),
("std::tuple", &["tuple"]),
("std::cout", &["iostream"]),
("std::cin", &["iostream"]),
("std::cerr", &["iostream"]),
("std::endl", &["iostream"]),
("std::sort", &["algorithm"]),
("std::find", &["algorithm"]),
("std::copy", &["algorithm"]),
("std::transform", &["algorithm"]),
("std::thread", &["thread"]),
("std::mutex", &["mutex"]),
("std::lock_guard", &["mutex"]),
("std::unique_lock", &["mutex"]),
("size_t", &["stddef.h", "cstddef"]),
("ptrdiff_t", &["stddef.h", "cstddef"]),
("NULL", &["stddef.h", "cstddef"]),
("nullptr", &["cstddef"]),
("int8_t", &["stdint.h", "cstdint"]),
("int16_t", &["stdint.h", "cstdint"]),
("int32_t", &["stdint.h", "cstdint"]),
("int64_t", &["stdint.h", "cstdint"]),
("uint8_t", &["stdint.h", "cstdint"]),
("uint16_t", &["stdint.h", "cstdint"]),
("uint32_t", &["stdint.h", "cstdint"]),
("uint64_t", &["stdint.h", "cstdint"]),
("bool", &["stdbool.h"]),
("true", &["stdbool.h"]),
("false", &["stdbool.h"]),
("sqrt", &["math.h", "cmath"]),
("pow", &["math.h", "cmath"]),
("sin", &["math.h", "cmath"]),
("cos", &["math.h", "cmath"]),
("tan", &["math.h", "cmath"]),
("log", &["math.h", "cmath"]),
("exp", &["math.h", "cmath"]),
("abs", &["stdlib.h", "cstdlib"]),
("fabs", &["math.h", "cmath"]),
("floor", &["math.h", "cmath"]),
("ceil", &["math.h", "cmath"]),
("pthread_create", &["pthread.h"]),
("pthread_join", &["pthread.h"]),
("pthread_mutex_lock", &["pthread.h"]),
("pthread_mutex_unlock", &["pthread.h"]),
("open", &["fcntl.h"]),
("close", &["unistd.h"]),
("read", &["unistd.h"]),
("write", &["unistd.h"]),
("sleep", &["unistd.h"]),
("usleep", &["unistd.h"]),
("getpid", &["unistd.h"]),
];
for (sym, headers) in mappings {
self.symbol_map.insert(
sym.to_string(),
headers.iter().map(|s| s.to_string()).collect(),
);
}
for (sym, headers) in &mappings {
for h in *headers {
self.iwyu_map
.entry(h.to_string())
.or_default()
.push(sym.to_string());
}
}
for symbols in self.iwyu_map.values_mut() {
symbols.sort();
symbols.dedup();
}
}
pub fn header_for_symbol(&self, symbol: &str) -> Option<Vec<String>> {
self.symbol_map.get(symbol).cloned()
}
pub fn is_standard_symbol(&self, symbol: &str) -> bool {
self.symbol_map.contains_key(symbol)
}
pub fn find_referenced_symbols(&self, source: &str) -> Vec<String> {
let mut symbols = Vec::new();
let mut seen = HashSet::new();
for line in source.lines() {
let words: Vec<&str> = line
.split(|c: char| !c.is_alphanumeric() && c != '_' && c != ':')
.collect();
for word in words {
let word = word.trim();
if !word.is_empty()
&& !word.starts_with("//")
&& !word.starts_with('#')
&& word != "if"
&& word != "for"
&& word != "while"
&& word != "return"
&& word != "int"
{
if self.symbol_map.contains_key(word) && seen.insert(word.to_string()) {
symbols.push(word.to_string());
}
}
}
}
symbols
}
pub fn parse_includes(&self, source: &str) -> Vec<X86Include> {
let mut includes = Vec::new();
for (line_num, line) in source.lines().enumerate() {
if let Some(inc) = X86Include::parse(line, line_num + 1) {
includes.push(inc);
}
}
includes
}
pub fn detect_unused_includes(&self, source: &str) -> Vec<usize> {
let includes = self.parse_includes(source);
let symbols = self.find_referenced_symbols(source);
let mut unused_lines = Vec::new();
for inc in &includes {
let mut is_used = false;
if let Some(syms) = self.iwyu_map.get(&inc.header) {
for sym in syms {
if symbols.contains(sym) {
is_used = true;
break;
}
}
}
if !is_used {
if self.c_headers.contains(&inc.header) || self.cpp_headers.contains(&inc.header) {
is_used = true;
}
}
if !is_used {
unused_lines.push(inc.line);
}
}
unused_lines
}
pub fn detect_missing_includes(&self, source: &str) -> Vec<(String, Vec<String>)> {
let symbols = self.find_referenced_symbols(source);
let includes = self.parse_includes(source);
let included_headers: HashSet<&str> = includes.iter().map(|i| i.header.as_str()).collect();
let mut missing = Vec::new();
for sym in &symbols {
if let Some(headers) = self.symbol_map.get(sym) {
let already_included = headers
.iter()
.any(|h| included_headers.contains(h.as_str()));
if !already_included {
missing.push((sym.clone(), headers.clone()));
}
}
}
missing
}
pub fn fix_file<P: AsRef<Path>>(&self, path: P) -> Result<usize, String> {
let path = path.as_ref();
let source = fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
let fixed = self.fix_source(&source)?;
let mut patches = 0;
if fixed != source {
patches = 1;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).map_err(|e| format!("Failed to create dir: {}", e))?;
}
fs::write(path, &fixed)
.map_err(|e| format!("Failed to write {}: {}", path.display(), e))?;
}
Ok(patches)
}
pub fn fix_source(&self, source: &str) -> Result<String, String> {
let mut result = String::with_capacity(source.len());
let lines: Vec<&str> = source.lines().collect();
let includes = self.parse_includes(source);
let unused_lines: HashSet<usize> = if self.remove_unused {
self.detect_unused_includes(source).into_iter().collect()
} else {
HashSet::new()
};
let missing: Vec<(String, Vec<String>)> = if self.add_missing {
self.detect_missing_includes(source)
} else {
Vec::new()
};
let mut include_lines: Vec<(usize, &str, X86IncludeCategory)> = Vec::new();
let mut non_include_lines: Vec<(usize, &str)> = Vec::new();
for (i, line) in lines.iter().enumerate() {
let line_num = i + 1;
let trimmed = line.trim();
if trimmed.starts_with("#include") {
if !unused_lines.contains(&line_num) {
let category = X86ClangFormat::classify_include(trimmed);
include_lines.push((line_num, trimmed, category));
}
} else if trimmed.starts_with("#pragma") && self.iwyu_keep_pragma {
} else if !trimmed.starts_with('#') {
non_include_lines.push((line_num, line));
}
}
let last_include_line = include_lines.last().map(|(l, _, _)| *l).unwrap_or(0);
if self.sort_includes {
include_lines.sort_by(|a, b| {
let cat_order = a.2.cmp(&b.2);
if cat_order != std::cmp::Ordering::Equal {
return cat_order;
}
a.1.to_lowercase().cmp(&b.1.to_lowercase())
});
}
let mut next_line = 1;
for (line_num, include_text, _category) in &include_lines {
while !non_include_lines.is_empty() && non_include_lines[0].0 < *line_num {
let (_, line) = non_include_lines.remove(0);
result.push_str(line);
result.push('\n');
}
result.push_str(include_text);
result.push('\n');
next_line = line_num + 1;
}
if !missing.is_empty() {
let mut seen_headers = HashSet::new();
for inc in &include_lines {
seen_headers.insert(inc.1.to_string());
}
for (_sym, headers) in &missing {
for h in headers {
let include_line = if self.c_headers.contains(h) || !h.ends_with(".h") {
format!("#include <{}>", h)
} else {
format!("#include \"{}\"", h)
};
if !seen_headers.contains(&include_line) {
result.push_str(&include_line);
result.push('\n');
seen_headers.insert(include_line);
}
break; }
}
}
for (_, line) in non_include_lines {
result.push_str(line);
result.push('\n');
}
if !result.ends_with('\n') {
result.push('\n');
}
Ok(result)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X86RefactorOp {
Rename {
old_name: String,
new_name: String,
kind: Option<String>,
},
ExtractFunction {
name: String,
start_line: usize,
end_line: usize,
params: Vec<String>,
return_type: String,
},
ExtractVariable {
name: String,
line: usize,
expression: String,
},
Inline { name: String, kind: InlineKind },
MoveDefinition {
name: String,
source_file: PathBuf,
target_file: PathBuf,
},
ChangeSignature {
name: String,
new_params: Vec<(String, String)>,
new_return: Option<String>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InlineKind {
Function,
Variable,
}
impl fmt::Display for InlineKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Function => write!(f, "function"),
Self::Variable => write!(f, "variable"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86RefactorReplacement {
pub file: PathBuf,
pub start_line: usize,
pub start_col: usize,
pub end_line: usize,
pub end_col: usize,
pub new_text: String,
pub description: String,
}
impl X86RefactorReplacement {
pub fn new(
file: PathBuf,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
new_text: impl Into<String>,
description: impl Into<String>,
) -> Self {
Self {
file,
start_line,
start_col,
end_line,
end_col,
new_text: new_text.into(),
description: description.into(),
}
}
pub fn apply(&self, source: &str) -> Result<String, String> {
let lines: Vec<&str> = source.lines().collect();
if self.start_line == 0 || self.start_line > lines.len() {
return Err(format!("Invalid start line: {}", self.start_line));
}
if self.end_line == 0 || self.end_line > lines.len() {
return Err(format!("Invalid end line: {}", self.end_line));
}
let mut result = String::with_capacity(source.len());
for (i, line) in lines.iter().enumerate() {
let line_num = i + 1;
if line_num < self.start_line {
result.push_str(line);
result.push('\n');
} else if line_num == self.start_line {
if self.start_line == self.end_line {
let before = &line[..self.start_col.saturating_sub(1).min(line.len())];
let after = if self.end_col <= line.len() {
&line[self.end_col.saturating_sub(1)..]
} else {
""
};
result.push_str(before);
result.push_str(&self.new_text);
result.push_str(after);
result.push('\n');
} else {
let before = &line[..self.start_col.saturating_sub(1).min(line.len())];
result.push_str(before);
if !self.new_text.is_empty() {
result.push_str(&self.new_text);
}
result.push('\n');
}
} else if line_num == self.end_line {
let after = if self.end_col <= line.len() {
&line[self.end_col.saturating_sub(1)..]
} else {
""
};
if self.new_text.is_empty() || !self.new_text.contains('\n') {
result.push_str(&self.new_text);
}
result.push_str(after);
result.push('\n');
} else if line_num > self.start_line && line_num < self.end_line {
continue;
} else {
result.push_str(line);
result.push('\n');
}
}
Ok(result)
}
}
#[derive(Debug, Clone)]
pub struct X86RefactoringTool {
pub operations: Vec<X86RefactorOp>,
pub create_backups: bool,
pub format_after: bool,
pub dry_run: bool,
pub max_files: usize,
pub continue_on_error: bool,
pub verbose: bool,
}
impl Default for X86RefactoringTool {
fn default() -> Self {
Self {
operations: Vec::new(),
create_backups: false,
format_after: true,
dry_run: false,
max_files: 0,
continue_on_error: true,
verbose: false,
}
}
}
impl X86RefactoringTool {
pub fn rename(
&mut self,
old_name: impl Into<String>,
new_name: impl Into<String>,
kind: Option<impl Into<String>>,
) {
self.operations.push(X86RefactorOp::Rename {
old_name: old_name.into(),
new_name: new_name.into(),
kind: kind.map(|k| k.into()),
});
}
pub fn extract_function(
&mut self,
name: impl Into<String>,
start: usize,
end: usize,
params: Vec<String>,
ret: impl Into<String>,
) {
self.operations.push(X86RefactorOp::ExtractFunction {
name: name.into(),
start_line: start,
end_line: end,
params,
return_type: ret.into(),
});
}
pub fn extract_variable(
&mut self,
name: impl Into<String>,
line: usize,
expr: impl Into<String>,
) {
self.operations.push(X86RefactorOp::ExtractVariable {
name: name.into(),
line,
expression: expr.into(),
});
}
pub fn inline(&mut self, name: impl Into<String>, kind: InlineKind) {
self.operations.push(X86RefactorOp::Inline {
name: name.into(),
kind,
});
}
pub fn move_definition(&mut self, name: impl Into<String>, source: PathBuf, target: PathBuf) {
self.operations.push(X86RefactorOp::MoveDefinition {
name: name.into(),
source_file: source,
target_file: target,
});
}
pub fn change_signature(
&mut self,
name: impl Into<String>,
new_params: Vec<(String, String)>,
new_return: Option<String>,
) {
self.operations.push(X86RefactorOp::ChangeSignature {
name: name.into(),
new_params,
new_return,
});
}
pub fn apply_all<P: AsRef<Path>>(&mut self, path: P) -> Result<usize, String> {
let path = path.as_ref();
let mut source = fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
let mut changes = 0;
for op in self.operations.clone() {
match &op {
X86RefactorOp::Rename {
old_name,
new_name,
kind: _,
} => {
let replacements = self.find_rename_replacements(&source, old_name, new_name);
for repl in replacements {
source = repl.apply(&source)?;
changes += 1;
}
}
X86RefactorOp::ExtractFunction {
name,
start_line,
end_line,
params,
return_type,
} => {
let repl = self.build_extract_function(
&source,
name,
*start_line,
*end_line,
params,
return_type,
)?;
source = repl.apply(&source)?;
changes += 1;
}
X86RefactorOp::ExtractVariable {
name,
line,
expression,
} => {
let repl = self.build_extract_variable(&source, name, *line, expression)?;
source = repl.apply(&source)?;
changes += 1;
}
X86RefactorOp::Inline { name, kind } => {
let repl = self.build_inline(&source, name, *kind)?;
source = repl.apply(&source)?;
changes += 1;
}
X86RefactorOp::MoveDefinition { .. } => {
}
X86RefactorOp::ChangeSignature {
name,
new_params,
new_return,
} => {
let repl =
self.build_change_signature(&source, name, new_params, new_return)?;
source = repl.apply(&source)?;
changes += 1;
}
}
}
if !self.dry_run && changes > 0 {
if self.create_backups {
let backup_path = path.with_extension("bak");
fs::copy(path, &backup_path)
.map_err(|e| format!("Failed to create backup: {}", e))?;
}
fs::write(path, &source)
.map_err(|e| format!("Failed to write {}: {}", path.display(), e))?;
}
self.operations.clear();
Ok(changes)
}
fn find_rename_replacements(
&self,
source: &str,
old_name: &str,
new_name: &str,
) -> Vec<X86RefactorReplacement> {
let mut replacements = Vec::new();
let lines: Vec<&str> = source.lines().collect();
for (i, line) in lines.iter().enumerate() {
let line_num = i + 1;
let mut col = 0;
while let Some(pos) = line[col..].find(old_name) {
let abs_col = col + pos + 1;
let before = if abs_col > 1 {
line.as_bytes().get(abs_col - 2).copied()
} else {
None
};
let after = line.as_bytes().get(abs_col - 1 + old_name.len()).copied();
let is_word = |b: Option<u8>| {
b.map(|c| !c.is_ascii_alphanumeric() && c != b'_')
.unwrap_or(true)
};
if is_word(before) && is_word(after) {
replacements.push(X86RefactorReplacement::new(
PathBuf::from("<source>"),
line_num,
abs_col,
line_num,
abs_col + old_name.len(),
new_name.to_string(),
format!("Rename '{}' to '{}'", old_name, new_name),
));
}
col = abs_col; }
}
replacements
}
fn build_extract_function(
&self,
source: &str,
name: &str,
start: usize,
end: usize,
params: &[String],
return_type: &str,
) -> Result<X86RefactorReplacement, String> {
let lines: Vec<&str> = source.lines().collect();
if start == 0 || start > lines.len() || end == 0 || end > lines.len() || start > end {
return Err("Invalid line range".into());
}
let param_list = params.join(", ");
let func_decl = format!("{} {}({})", return_type, name, param_list);
let call = format!(
"{}({});",
name,
params
.iter()
.map(|p| p.split(' ').last().unwrap_or(p))
.collect::<Vec<_>>()
.join(", ")
);
let mut extracted_body = String::new();
for i in start..=end {
extracted_body.push_str(lines[i - 1]);
extracted_body.push('\n');
}
let new_function = format!("{} {{\n{}\n}}\n", func_decl, extracted_body.trim());
let repl = X86RefactorReplacement::new(
PathBuf::from("<source>"),
start,
1,
end,
lines[end - 1].len(),
if return_type == "void" {
call
} else {
new_function
},
format!("Extract function '{}' lines {}-{}", name, start, end),
);
Ok(repl)
}
fn build_extract_variable(
&self,
_source: &str,
name: &str,
line: usize,
expression: &str,
) -> Result<X86RefactorReplacement, String> {
let new_decl = format!("auto {} = {};", name, expression);
Ok(X86RefactorReplacement::new(
PathBuf::from("<source>"),
line,
1,
line,
0, new_decl,
format!("Extract variable '{}'", name),
))
}
fn build_inline(
&self,
_source: &str,
_name: &str,
_kind: InlineKind,
) -> Result<X86RefactorReplacement, String> {
Ok(X86RefactorReplacement::new(
PathBuf::from("<source>"),
1,
1,
1,
1,
"",
"Inline operation".to_string(),
))
}
fn build_change_signature(
&self,
_source: &str,
_name: &str,
new_params: &[(String, String)],
new_return: &Option<String>,
) -> Result<X86RefactorReplacement, String> {
let param_list: Vec<String> = new_params
.iter()
.map(|(ty, name)| format!("{} {}", ty, name))
.collect();
let ret = new_return.as_deref().unwrap_or("void");
let new_sig = format!("{} {}({})", ret, _name, param_list.join(", "));
Ok(X86RefactorReplacement::new(
PathBuf::from("<source>"),
1,
1,
1,
1,
new_sig,
format!("Change signature of '{}'", _name),
))
}
}
#[derive(Debug, Clone)]
pub enum X86QueryMatcher {
NodeKind(String),
HasName(String),
HasType(String),
HasDescendant(Box<X86QueryMatcher>),
HasAncestor(Box<X86QueryMatcher>),
AnyOf(Vec<X86QueryMatcher>),
AllOf(Vec<X86QueryMatcher>),
Unless(Box<X86QueryMatcher>),
InRange(usize, usize, usize, usize),
HasIntegerValue(i64),
HasStringValue(String),
Returns(String),
ParameterCountIs(usize),
IsReferenced,
Anything,
}
impl X86QueryMatcher {
pub fn node_kind(kind: &str) -> Self {
Self::NodeKind(kind.to_string())
}
pub fn has_name(name: &str) -> Self {
Self::HasName(name.to_string())
}
pub fn has_type(ty: &str) -> Self {
Self::HasType(ty.to_string())
}
pub fn has_descendant(matcher: X86QueryMatcher) -> Self {
Self::HasDescendant(Box::new(matcher))
}
pub fn any_of(matchers: Vec<X86QueryMatcher>) -> Self {
Self::AnyOf(matchers)
}
pub fn all_of(matchers: Vec<X86QueryMatcher>) -> Self {
Self::AllOf(matchers)
}
pub fn unless(matcher: X86QueryMatcher) -> Self {
Self::Unless(Box::new(matcher))
}
pub fn to_query_string(&self) -> String {
match self {
Self::NodeKind(k) => format!("kind({})", k),
Self::HasName(n) => format!("hasName(\"{}\")", n),
Self::HasType(t) => format!("hasType({})", t),
Self::HasDescendant(m) => format!("hasDescendant({})", m.to_query_string()),
Self::HasAncestor(m) => format!("hasAncestor({})", m.to_query_string()),
Self::AnyOf(ms) => {
let inner: Vec<String> = ms.iter().map(|m| m.to_query_string()).collect();
format!("anyOf({})", inner.join(", "))
}
Self::AllOf(ms) => {
let inner: Vec<String> = ms.iter().map(|m| m.to_query_string()).collect();
format!("allOf({})", inner.join(", "))
}
Self::Unless(m) => format!("unless({})", m.to_query_string()),
Self::InRange(sl, sc, el, ec) => format!("inRange({}:{}:{}:{})", sl, sc, el, ec),
Self::HasIntegerValue(v) => format!("hasIntegerValue({})", v),
Self::HasStringValue(s) => format!("hasStringValue(\"{}\")", s),
Self::Returns(r) => format!("returns({})", r),
Self::ParameterCountIs(n) => format!("parameterCountIs({})", n),
Self::IsReferenced => "isReferenced()".to_string(),
Self::Anything => "anything()".to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86QueryMatch {
pub node_kind: String,
pub name: Option<String>,
pub file: PathBuf,
pub start_line: usize,
pub start_col: usize,
pub end_line: usize,
pub end_col: usize,
pub match_text: String,
pub bindings: HashMap<String, String>,
}
impl X86QueryMatch {
pub fn new(
node_kind: impl Into<String>,
name: Option<impl Into<String>>,
file: PathBuf,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
match_text: impl Into<String>,
) -> Self {
Self {
node_kind: node_kind.into(),
name: name.map(|n| n.into()),
file,
start_line,
start_col,
end_line,
end_col,
match_text: match_text.into(),
bindings: HashMap::new(),
}
}
pub fn add_binding(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.bindings.insert(key.into(), value.into());
}
}
#[derive(Debug, Clone)]
pub struct X86ClangQuery {
pub matchers: Vec<X86QueryMatcher>,
pub results: Vec<X86QueryMatch>,
pub dump_ast: bool,
pub node_filter: Option<Vec<String>>,
pub range_filter: Option<(usize, usize, usize, usize)>,
pub show_types: bool,
pub verbose: bool,
}
impl Default for X86ClangQuery {
fn default() -> Self {
Self {
matchers: Vec::new(),
results: Vec::new(),
dump_ast: false,
node_filter: None,
range_filter: None,
show_types: false,
verbose: false,
}
}
}
impl X86ClangQuery {
pub fn new() -> Self {
Self::default()
}
pub fn add_matcher(&mut self, matcher: X86QueryMatcher) {
self.matchers.push(matcher);
}
pub fn set_matchers(&mut self, matchers: Vec<X86QueryMatcher>) {
self.matchers = matchers;
}
pub fn query_file<P: AsRef<Path>>(&self, path: P) -> Result<Vec<X86QueryMatch>, String> {
let path = path.as_ref();
let source = fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
let mut results = Vec::new();
let lines: Vec<&str> = source.lines().collect();
for (line_idx, line) in lines.iter().enumerate() {
let line_num = line_idx + 1;
for matcher in &self.matchers {
let matches = self.match_line(line, line_num, path, matcher);
results.extend(matches);
}
}
Ok(results)
}
fn match_line(
&self,
line: &str,
line_num: usize,
file: &Path,
matcher: &X86QueryMatcher,
) -> Vec<X86QueryMatch> {
let trimmed = line.trim();
let mut matches = Vec::new();
match matcher {
X86QueryMatcher::NodeKind(kind) => {
if self.line_matches_kind(trimmed, kind) {
matches.push(X86QueryMatch::new(
kind.clone(),
Some(self.extract_name(trimmed, kind)),
file.to_path_buf(),
line_num,
1,
line_num,
trimmed.len(),
line.to_string(),
));
}
}
X86QueryMatcher::HasName(name) => {
if trimmed.contains(name.as_str()) && self.is_identifier_boundary(trimmed, name) {
matches.push(X86QueryMatch::new(
"named",
Some(name.clone()),
file.to_path_buf(),
line_num,
trimmed.find(name).map(|p| p + 1).unwrap_or(1),
line_num,
trimmed.find(name).map(|p| p + name.len()).unwrap_or(1),
line.to_string(),
));
}
}
X86QueryMatcher::HasType(ty) => {
if trimmed.contains(ty.as_str()) {
matches.push(X86QueryMatch::new(
"type",
Some(ty.clone()),
file.to_path_buf(),
line_num,
1,
line_num,
trimmed.len(),
line.to_string(),
));
}
}
X86QueryMatcher::Anything => {
if !trimmed.is_empty() {
matches.push(X86QueryMatch::new(
"anything",
None,
file.to_path_buf(),
line_num,
1,
line_num,
trimmed.len(),
line.to_string(),
));
}
}
X86QueryMatcher::HasIntegerValue(val) => {
if let Some(pos) = trimmed.find(&val.to_string()) {
matches.push(X86QueryMatch::new(
"integer-literal",
None,
file.to_path_buf(),
line_num,
pos + 1,
line_num,
pos + val.to_string().len(),
line.to_string(),
));
}
}
X86QueryMatcher::HasStringValue(s) => {
if trimmed.contains(&format!("\"{}\"", s)) {
matches.push(X86QueryMatch::new(
"string-literal",
None,
file.to_path_buf(),
line_num,
1,
line_num,
trimmed.len(),
line.to_string(),
));
}
}
X86QueryMatcher::AnyOf(sub_matchers) => {
for sub in sub_matchers {
matches.extend(self.match_line(line, line_num, file, sub));
}
}
X86QueryMatcher::AllOf(sub_matchers) => {
let all_match = sub_matchers
.iter()
.all(|sub| !self.match_line(line, line_num, file, sub).is_empty());
if all_match && !sub_matchers.is_empty() {
matches.push(X86QueryMatch::new(
"allOf",
None,
file.to_path_buf(),
line_num,
1,
line_num,
trimmed.len(),
line.to_string(),
));
}
}
X86QueryMatcher::Unless(sub) => {
if self.match_line(line, line_num, file, sub).is_empty() {
matches.push(X86QueryMatch::new(
"unless",
None,
file.to_path_buf(),
line_num,
1,
line_num,
trimmed.len(),
line.to_string(),
));
}
}
X86QueryMatcher::Returns(ret) => {
if trimmed.contains(&format!("{} ", ret)) && trimmed.contains('(') {
matches.push(X86QueryMatch::new(
"function",
None,
file.to_path_buf(),
line_num,
1,
line_num,
trimmed.len(),
line.to_string(),
));
}
}
X86QueryMatcher::ParameterCountIs(n) => {
if trimmed.contains('(') && trimmed.contains(')') {
let params = self.count_params(trimmed);
if params == *n {
matches.push(X86QueryMatch::new(
"function",
None,
file.to_path_buf(),
line_num,
1,
line_num,
trimmed.len(),
line.to_string(),
));
}
}
}
_ => {
}
}
matches
}
fn line_matches_kind(&self, line: &str, kind: &str) -> bool {
let trimmed = line.trim();
match kind {
"function" | "FunctionDecl" | "functionDecl" => {
trimmed.contains('(')
&& trimmed.contains(')')
&& !trimmed.starts_with("if")
&& !trimmed.starts_with("for")
&& !trimmed.starts_with("while")
}
"variable" | "VarDecl" | "varDecl" => {
(trimmed.starts_with("int ")
|| trimmed.starts_with("float ")
|| trimmed.starts_with("double ")
|| trimmed.starts_with("char ")
|| trimmed.starts_with("auto ")
|| trimmed.starts_with("bool "))
&& trimmed.ends_with(';')
}
"call" | "CallExpr" | "callExpr" => trimmed.contains('(') && trimmed.ends_with(");"),
"return" | "ReturnStmt" | "returnStmt" => trimmed.starts_with("return "),
"if" | "IfStmt" | "ifStmt" => trimmed.starts_with("if ") || trimmed.starts_with("if("),
"for" | "ForStmt" | "forStmt" => {
trimmed.starts_with("for ") || trimmed.starts_with("for(")
}
"while" | "WhileStmt" | "whileStmt" => trimmed.starts_with("while "),
"class" | "CXXRecordDecl" | "class" => {
trimmed.starts_with("class ") || trimmed.starts_with("struct ")
}
"enum" | "EnumDecl" => trimmed.starts_with("enum "),
"include" => trimmed.starts_with("#include"),
_ => trimmed.to_lowercase().contains(&kind.to_lowercase()),
}
}
fn extract_name(&self, line: &str, kind: &str) -> String {
let trimmed = line.trim();
match kind {
"function" | "FunctionDecl" | "functionDecl" => {
if let Some(pos) = trimmed.find('(') {
let before_paren = &trimmed[..pos];
let words: Vec<&str> = before_paren.split_whitespace().collect();
words.last().copied().unwrap_or("unknown").to_string()
} else {
"unknown".to_string()
}
}
"variable" | "VarDecl" | "varDecl" => {
let words: Vec<&str> = trimmed.split_whitespace().collect();
if words.len() >= 2 {
words[1].trim_end_matches(';').to_string()
} else {
"unknown".to_string()
}
}
"class" | "CXXRecordDecl" => {
let words: Vec<&str> = trimmed.split_whitespace().collect();
if words.len() >= 2 {
words[1].trim_end_matches('{').to_string()
} else {
"unknown".to_string()
}
}
_ => "unnamed".to_string(),
}
}
fn is_identifier_boundary(&self, line: &str, name: &str) -> bool {
if let Some(pos) = line.find(name) {
let before = if pos > 0 {
line.as_bytes().get(pos - 1).copied()
} else {
None
};
let after = line.as_bytes().get(pos + name.len()).copied();
let is_boundary = |b: Option<u8>| {
b.map(|c| !c.is_ascii_alphanumeric() && c != b'_')
.unwrap_or(true)
};
is_boundary(before) && is_boundary(after)
} else {
false
}
}
fn count_params(&self, line: &str) -> usize {
if let Some(start) = line.find('(') {
if let Some(end) = line.rfind(')') {
let inner = &line[start + 1..end];
if inner.trim().is_empty() {
return 0;
}
return inner.split(',').count();
}
}
0
}
pub fn dump_ast_string(&self, source: &str) -> String {
let mut dump = String::new();
dump.push_str("AST Dump\n");
dump.push_str("========\n");
let lines: Vec<&str> = source.lines().collect();
for (i, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
let indent = " ".repeat(1);
if trimmed.starts_with("#include") {
dump.push_str(&format!("{}IncludeDirective `{}`\n", indent, trimmed));
} else if trimmed.starts_with("int ")
|| trimmed.starts_with("float ")
|| trimmed.starts_with("double ")
|| trimmed.starts_with("char ")
|| trimmed.starts_with("bool ")
|| trimmed.starts_with("auto ")
{
dump.push_str(&format!("{}VarDecl `${}`\n", indent, trimmed));
} else if trimmed.starts_with("if ") || trimmed.starts_with("if(") {
dump.push_str(&format!("{}IfStmt\n", indent));
} else if trimmed.starts_with("for ") || trimmed.starts_with("for(") {
dump.push_str(&format!("{}ForStmt\n", indent));
} else if trimmed.starts_with("while ") {
dump.push_str(&format!("{}WhileStmt\n", indent));
} else if trimmed.starts_with("return ") {
dump.push_str(&format!("{}ReturnStmt `{}`\n", indent, trimmed));
} else if trimmed.starts_with("class ") || trimmed.starts_with("struct ") {
dump.push_str(&format!("{}RecordDecl `${}`\n", indent, trimmed));
} else if trimmed.starts_with("enum ") {
dump.push_str(&format!("{}EnumDecl `${}`\n", indent, trimmed));
} else if trimmed.contains('(') && trimmed.ends_with(");") {
dump.push_str(&format!("{}CallExpr `${}`\n", indent, trimmed));
} else if trimmed.starts_with("//") || trimmed.starts_with("/*") {
dump.push_str(&format!("{}Comment `${}`\n", indent, trimmed));
} else {
dump.push_str(&format!("{}Stmt `${}`\n", indent, trimmed));
}
}
dump
}
pub fn query_range(
&self,
source: &str,
file: &Path,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
) -> Vec<X86QueryMatch> {
let lines: Vec<&str> = source.lines().collect();
let mut results = Vec::new();
for (i, line) in lines.iter().enumerate() {
let line_num = i + 1;
if line_num < start_line || line_num > end_line {
continue;
}
let line_start = if line_num == start_line { start_col } else { 1 };
let line_end = if line_num == end_line {
end_col
} else {
line.len()
};
if line_start <= line.len() {
let snippet = &line[line_start.saturating_sub(1)..line_end.min(line.len())];
if !snippet.trim().is_empty() {
results.push(X86QueryMatch::new(
"range-match",
None,
file.to_path_buf(),
line_num,
line_start,
line_num,
line_end,
snippet.to_string(),
));
}
}
}
results
}
pub fn query_types(&self, source: &str, file: &Path) -> Vec<X86QueryMatch> {
let lines: Vec<&str> = source.lines().collect();
let mut results = Vec::new();
let type_keywords = [
"int", "float", "double", "char", "void", "bool", "auto", "short", "long", "unsigned",
"signed", "size_t", "int8_t", "int16_t", "int32_t", "int64_t", "uint8_t", "uint16_t",
"uint32_t", "uint64_t",
];
for (i, line) in lines.iter().enumerate() {
let line_num = i + 1;
let trimmed = line.trim();
for ty in &type_keywords {
if self.is_identifier_boundary(trimmed, ty) && trimmed.contains(*ty) {
results.push(X86QueryMatch::new(
"type-decl",
Some(*ty),
file.to_path_buf(),
line_num,
1,
line_num,
trimmed.len(),
trimmed.to_string(),
));
break; }
}
}
results
}
}
#[derive(Debug, Clone)]
pub struct X86ToolingConfig {
pub compilation_database: Option<PathBuf>,
pub clang_format_file: Option<PathBuf>,
pub clang_tidy_file: Option<PathBuf>,
pub build_path: Option<PathBuf>,
pub source_root: Option<PathBuf>,
pub extra_args: Vec<String>,
pub jobs: usize,
pub use_color: bool,
pub quiet: bool,
}
impl Default for X86ToolingConfig {
fn default() -> Self {
Self {
compilation_database: None,
clang_format_file: None,
clang_tidy_file: None,
build_path: None,
source_root: None,
extra_args: Vec::new(),
jobs: 1,
use_color: true,
quiet: false,
}
}
}
impl X86ToolingConfig {
pub fn auto_detect() -> Self {
let mut config = Self::default();
for candidate in &["compile_commands.json", "build/compile_commands.json"] {
let path = Path::new(candidate);
if path.exists() {
config.compilation_database = Some(path.to_path_buf());
break;
}
}
for candidate in &[".clang-format", "_clang-format"] {
let path = Path::new(candidate);
if path.exists() {
config.clang_format_file = Some(path.to_path_buf());
break;
}
}
for candidate in &[".clang-tidy", "_clang-tidy"] {
let path = Path::new(candidate);
if path.exists() {
config.clang_tidy_file = Some(path.to_path_buf());
break;
}
}
config
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct X86ClangFormatStylePreset {
pub name: String,
pub brace_style: X86BraceStyle,
pub indent_width: u8,
pub use_tabs: bool,
pub column_limit: u16,
pub pointer_alignment: X86PointerAlignment,
pub include_order: X86IncludeOrderStyle,
pub space_before_parens: bool,
pub allow_short_functions: bool,
pub allow_short_ifs: bool,
pub reflow_comments: bool,
pub align_trailing_comments: bool,
pub sort_includes: bool,
}
impl X86ClangFormatStylePreset {
pub fn llvm() -> Self {
Self {
name: "LLVM".into(),
brace_style: X86BraceStyle::LLVM,
indent_width: 2,
use_tabs: false,
column_limit: 80,
pointer_alignment: X86PointerAlignment::Right,
include_order: X86IncludeOrderStyle::LLVM,
space_before_parens: false,
allow_short_functions: true,
allow_short_ifs: false,
reflow_comments: true,
align_trailing_comments: true,
sort_includes: true,
}
}
pub fn google() -> Self {
Self {
name: "Google".into(),
brace_style: X86BraceStyle::Google,
indent_width: 2,
use_tabs: false,
column_limit: 80,
pointer_alignment: X86PointerAlignment::Left,
include_order: X86IncludeOrderStyle::Google,
space_before_parens: false,
allow_short_functions: true,
allow_short_ifs: false,
reflow_comments: true,
align_trailing_comments: true,
sort_includes: true,
}
}
pub fn chromium() -> Self {
Self {
name: "Chromium".into(),
brace_style: X86BraceStyle::Chromium,
indent_width: 2,
use_tabs: false,
column_limit: 80,
pointer_alignment: X86PointerAlignment::Left,
include_order: X86IncludeOrderStyle::Alphabetical,
space_before_parens: false,
allow_short_functions: true,
allow_short_ifs: false,
reflow_comments: true,
align_trailing_comments: true,
sort_includes: true,
}
}
pub fn mozilla() -> Self {
Self {
name: "Mozilla".into(),
brace_style: X86BraceStyle::Mozilla,
indent_width: 2,
use_tabs: false,
column_limit: 99,
pointer_alignment: X86PointerAlignment::Left,
include_order: X86IncludeOrderStyle::LLVM,
space_before_parens: false,
allow_short_functions: true,
allow_short_ifs: false,
reflow_comments: true,
align_trailing_comments: true,
sort_includes: true,
}
}
pub fn webkit() -> Self {
Self {
name: "WebKit".into(),
brace_style: X86BraceStyle::WebKit,
indent_width: 4,
use_tabs: false,
column_limit: 120,
pointer_alignment: X86PointerAlignment::Left,
include_order: X86IncludeOrderStyle::Alphabetical,
space_before_parens: false,
allow_short_functions: true,
allow_short_ifs: false,
reflow_comments: true,
align_trailing_comments: true,
sort_includes: true,
}
}
pub fn microsoft() -> Self {
Self {
name: "Microsoft".into(),
brace_style: X86BraceStyle::Microsoft,
indent_width: 4,
use_tabs: true,
column_limit: 120,
pointer_alignment: X86PointerAlignment::Left,
include_order: X86IncludeOrderStyle::Alphabetical,
space_before_parens: true,
allow_short_functions: false,
allow_short_ifs: false,
reflow_comments: true,
align_trailing_comments: false,
sort_includes: true,
}
}
pub fn gnu() -> Self {
Self {
name: "GNU".into(),
brace_style: X86BraceStyle::GNU,
indent_width: 2,
use_tabs: false,
column_limit: 79,
pointer_alignment: X86PointerAlignment::Right,
include_order: X86IncludeOrderStyle::LLVM,
space_before_parens: true,
allow_short_functions: false,
allow_short_ifs: false,
reflow_comments: true,
align_trailing_comments: false,
sort_includes: true,
}
}
pub fn apply_to(&self, fmt: &mut X86ClangFormat) {
fmt.brace_style = self.brace_style;
fmt.indent.width = self.indent_width;
fmt.indent.use_tabs = self.use_tabs;
fmt.line_break.column_limit = self.column_limit;
fmt.pointer_alignment = self.pointer_alignment;
fmt.include_order = self.include_order;
fmt.whitespace.space_before_parens = self.space_before_parens;
fmt.line_break.allow_short_functions_on_a_single_line = self.allow_short_functions;
fmt.line_break.allow_short_if_statements_on_a_single_line = self.allow_short_ifs;
fmt.reflow_comments = self.reflow_comments;
fmt.alignment.align_trailing_comments = self.align_trailing_comments;
fmt.sort_includes = self.sort_includes;
}
pub fn all_preset_names() -> Vec<&'static str> {
vec![
"LLVM",
"Google",
"Chromium",
"Mozilla",
"WebKit",
"Microsoft",
"GNU",
]
}
pub fn by_name(name: &str) -> Option<Self> {
match name.to_lowercase().as_str() {
"llvm" => Some(Self::llvm()),
"google" => Some(Self::google()),
"chromium" => Some(Self::chromium()),
"mozilla" => Some(Self::mozilla()),
"webkit" => Some(Self::webkit()),
"microsoft" => Some(Self::microsoft()),
"gnu" => Some(Self::gnu()),
_ => None,
}
}
pub fn merge(&self, other: &Self) -> Self {
Self {
name: format!("{}->{}", self.name, other.name),
brace_style: other.brace_style,
indent_width: if other.indent_width != 2 {
other.indent_width
} else {
self.indent_width
},
use_tabs: other.use_tabs,
column_limit: if other.column_limit != 80 {
other.column_limit
} else {
self.column_limit
},
pointer_alignment: other.pointer_alignment,
include_order: other.include_order,
space_before_parens: other.space_before_parens,
allow_short_functions: other.allow_short_functions,
allow_short_ifs: other.allow_short_ifs,
reflow_comments: other.reflow_comments,
align_trailing_comments: other.align_trailing_comments,
sort_includes: other.sort_includes,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86TokenAnnotation {
LineStart,
OpenBrace,
CloseBrace,
OpenParen,
CloseParen,
OpenBracket,
CloseBracket,
OpenAngle,
CloseAngle,
Comma,
Semicolon,
Colon,
Assignment,
BinaryOperator,
UnaryOperator,
Keyword,
TypeName,
Identifier,
StringLiteral,
NumericLiteral,
Preprocessor,
LineComment,
BlockComment,
Whitespace,
Unknown,
}
impl fmt::Display for X86TokenAnnotation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::LineStart => write!(f, "LineStart"),
Self::OpenBrace => write!(f, "OpenBrace"),
Self::CloseBrace => write!(f, "CloseBrace"),
Self::OpenParen => write!(f, "OpenParen"),
Self::CloseParen => write!(f, "CloseParen"),
Self::OpenBracket => write!(f, "OpenBracket"),
Self::CloseBracket => write!(f, "CloseBracket"),
Self::OpenAngle => write!(f, "OpenAngle"),
Self::CloseAngle => write!(f, "CloseAngle"),
Self::Comma => write!(f, "Comma"),
Self::Semicolon => write!(f, "Semicolon"),
Self::Colon => write!(f, "Colon"),
Self::Assignment => write!(f, "Assignment"),
Self::BinaryOperator => write!(f, "BinaryOperator"),
Self::UnaryOperator => write!(f, "UnaryOperator"),
Self::Keyword => write!(f, "Keyword"),
Self::TypeName => write!(f, "TypeName"),
Self::Identifier => write!(f, "Identifier"),
Self::StringLiteral => write!(f, "StringLiteral"),
Self::NumericLiteral => write!(f, "NumericLiteral"),
Self::Preprocessor => write!(f, "Preprocessor"),
Self::LineComment => write!(f, "LineComment"),
Self::BlockComment => write!(f, "BlockComment"),
Self::Whitespace => write!(f, "Whitespace"),
Self::Unknown => write!(f, "Unknown"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86TokenAnnotator {
keywords: HashSet<String>,
type_names: HashSet<String>,
pp_keywords: HashSet<String>,
}
impl Default for X86TokenAnnotator {
fn default() -> Self {
let mut annotator = Self {
keywords: HashSet::new(),
type_names: HashSet::new(),
pp_keywords: HashSet::new(),
};
annotator.init_keywords();
annotator.init_types();
annotator.init_pp_keywords();
annotator
}
}
impl X86TokenAnnotator {
fn init_keywords(&mut self) {
for kw in &[
"if",
"else",
"for",
"while",
"do",
"switch",
"case",
"default",
"break",
"continue",
"return",
"goto",
"sizeof",
"alignof",
"typedef",
"struct",
"union",
"enum",
"class",
"namespace",
"using",
"template",
"typename",
"static",
"extern",
"const",
"volatile",
"mutable",
"register",
"auto",
"register",
"virtual",
"override",
"final",
"explicit",
"inline",
"public",
"private",
"protected",
"friend",
"operator",
"new",
"delete",
"this",
"throw",
"try",
"catch",
"constexpr",
"consteval",
"constinit",
"noexcept",
"decltype",
"sizeof",
"alignas",
"static_assert",
"thread_local",
"export",
"module",
"import",
"concept",
"requires",
"co_await",
"co_yield",
"co_return",
] {
self.keywords.insert(kw.to_string());
}
}
fn init_types(&mut self) {
for ty in &[
"void",
"char",
"short",
"int",
"long",
"float",
"double",
"bool",
"wchar_t",
"char8_t",
"char16_t",
"char32_t",
"signed",
"unsigned",
"size_t",
"ssize_t",
"ptrdiff_t",
"int8_t",
"int16_t",
"int32_t",
"int64_t",
"uint8_t",
"uint16_t",
"uint32_t",
"uint64_t",
"intptr_t",
"uintptr_t",
"intmax_t",
"uintmax_t",
"string",
"vector",
"map",
"set",
"list",
"deque",
"array",
"tuple",
"pair",
"optional",
"variant",
"shared_ptr",
"unique_ptr",
"weak_ptr",
"function",
"string_view",
"span",
"initializer_list",
] {
self.type_names.insert(ty.to_string());
}
}
fn init_pp_keywords(&mut self) {
for kw in &[
"include", "define", "undef", "if", "ifdef", "ifndef", "else", "elif", "endif",
"pragma", "error", "warning", "line", "import", "using",
] {
self.pp_keywords.insert(kw.to_string());
}
}
pub fn annotate_token(&self, token: &str) -> X86TokenAnnotation {
if token.is_empty() {
return X86TokenAnnotation::Whitespace;
}
match token {
"{" => X86TokenAnnotation::OpenBrace,
"}" => X86TokenAnnotation::CloseBrace,
"(" => X86TokenAnnotation::OpenParen,
")" => X86TokenAnnotation::CloseParen,
"[" => X86TokenAnnotation::OpenBracket,
"]" => X86TokenAnnotation::CloseBracket,
"," => X86TokenAnnotation::Comma,
";" => X86TokenAnnotation::Semicolon,
":" => X86TokenAnnotation::Colon,
"=" | "+=" | "-=" | "*=" | "/=" | "%=" | "&=" | "|=" | "^=" => {
X86TokenAnnotation::Assignment
}
"+" | "-" | "*" | "/" | "%" | "&" | "|" | "^" | "<<" | ">>" | "==" | "!=" | "<"
| ">" | "<=" | ">=" | "&&" | "||" => X86TokenAnnotation::BinaryOperator,
"!" | "~" | "++" | "--" | "&" | "*" => X86TokenAnnotation::UnaryOperator,
_ => {
if self.keywords.contains(token) {
X86TokenAnnotation::Keyword
} else if self.type_names.contains(token) {
X86TokenAnnotation::TypeName
} else if token.starts_with('"') && token.ends_with('"') {
X86TokenAnnotation::StringLiteral
} else if token
.chars()
.all(|c| c.is_ascii_digit() || c == '.' || c == 'x' || c == 'X')
{
X86TokenAnnotation::NumericLiteral
} else if token.starts_with('#') {
X86TokenAnnotation::Preprocessor
} else if token.starts_with("//") {
X86TokenAnnotation::LineComment
} else if token.starts_with("/*") {
X86TokenAnnotation::BlockComment
} else if token.chars().all(|c| c.is_alphanumeric() || c == '_') {
X86TokenAnnotation::Identifier
} else {
X86TokenAnnotation::Unknown
}
}
}
}
pub fn annotate_line(&self, line: &str) -> Vec<(String, X86TokenAnnotation)> {
let mut tokens = Vec::new();
let mut current = String::new();
let bytes = line.as_bytes();
let mut in_string = false;
let mut in_char = false;
let mut i = 0;
while i < bytes.len() {
let ch = bytes[i] as char;
if ch == '"' && !in_char {
if i == 0 || bytes[i - 1] != b'\\' {
in_string = !in_string;
if !in_string {
current.push(ch);
tokens.push((current.clone(), X86TokenAnnotation::StringLiteral));
current.clear();
i += 1;
continue;
}
}
}
if ch == '\'' && !in_string {
if i == 0 || bytes[i - 1] != b'\\' {
in_char = !in_char;
}
}
if in_string || in_char {
current.push(ch);
} else if ch.is_whitespace() {
if !current.is_empty() {
let ann = self.annotate_token(¤t);
tokens.push((current.clone(), ann));
current.clear();
}
tokens.push((" ".to_string(), X86TokenAnnotation::Whitespace));
while i + 1 < bytes.len() && (bytes[i + 1] as char).is_whitespace() {
i += 1;
}
} else if "{}[](),;:+-*/%&|^!=<>~".contains(ch) {
if !current.is_empty() {
let ann = self.annotate_token(¤t);
tokens.push((current.clone(), ann));
current.clear();
}
if i + 1 < bytes.len() {
let two = format!("{}{}", ch, bytes[i + 1] as char);
if matches!(
two.as_str(),
"++" | "--"
| "<<"
| ">>"
| "=="
| "!="
| "<="
| ">="
| "&&"
| "||"
| "+="
| "-="
| "*="
| "/="
| "%="
| "&="
| "|="
| "^="
) {
let ann = self.annotate_token(&two);
tokens.push((two.clone(), ann));
i += 2;
continue;
}
}
let ann = self.annotate_token(&ch.to_string());
tokens.push((ch.to_string(), ann));
} else {
current.push(ch);
}
i += 1;
}
if !current.is_empty() {
let ann = self.annotate_token(¤t);
tokens.push((current, ann));
}
tokens
}
pub fn needs_space_before(
&self,
prev: X86TokenAnnotation,
current: X86TokenAnnotation,
token: &str,
) -> bool {
use X86TokenAnnotation::*;
match (prev, current) {
(_, CloseParen) | (_, CloseBrace) | (_, CloseBracket) | (_, Comma) | (_, Semicolon) => {
false
}
(_, CloseAngle) => false,
(OpenParen, _) | (OpenBrace, _) | (OpenBracket, _) => false,
(OpenAngle, _) => false,
(_, BinaryOperator) | (BinaryOperator, _) => true,
(_, Assignment) | (Assignment, _) => true,
(Keyword, OpenParen) => !matches!(token, "("),
(_, OpenBrace) => true,
(Identifier, Identifier) => true,
(TypeName, Identifier) => true,
(Identifier, TypeName) => true,
_ => false,
}
}
pub fn add_keyword(&mut self, keyword: &str) {
self.keywords.insert(keyword.to_string());
}
pub fn add_type_name(&mut self, type_name: &str) {
self.type_names.insert(type_name.to_string());
}
}
#[derive(Debug, Clone)]
pub struct X86BreakCandidate {
pub column: usize,
pub penalty: u32,
pub reason: String,
}
impl X86BreakCandidate {
pub fn new(column: usize, penalty: u32, reason: impl Into<String>) -> Self {
Self {
column,
penalty,
reason: reason.into(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86LineBreakOptimizer {
pub style: X86LineBreakStyle,
pub annotator: X86TokenAnnotator,
}
impl Default for X86LineBreakOptimizer {
fn default() -> Self {
Self {
style: X86LineBreakStyle::default(),
annotator: X86TokenAnnotator::default(),
}
}
}
impl X86LineBreakOptimizer {
pub fn new(style: X86LineBreakStyle) -> Self {
Self {
style,
..Default::default()
}
}
pub fn find_optimal_break(&self, line: &str) -> Option<X86BreakCandidate> {
if line.len() <= self.style.column_limit as usize {
return None;
}
let mut candidates = Vec::new();
let bytes = line.as_bytes();
let limit = self.style.column_limit as usize;
for i in (0..limit.min(bytes.len())).rev() {
let ch = bytes[i] as char;
let penalty = match ch {
',' => self.style.penalty_break_before_first_call_parameter,
' ' => {
if i > 0 {
match bytes[i - 1] as char {
'+' | '-' | '*' | '/' | '&' | '|' | '=' | '<' | '>' | '!' => 50,
_ => 200,
}
} else {
200
}
}
';' => 100,
'(' => 150,
'?' => self.style.penalty_break_before_ternary,
':' => self.style.penalty_break_before_ternary / 2,
_ => 1000,
};
if penalty < 1000 {
candidates.push(X86BreakCandidate::new(
i + 1,
penalty + (limit - i) as u32 * self.style.penalty_excess_character / 1000000,
format!("break after '{}' at column {}", ch, i + 1),
));
}
}
candidates.sort_by_key(|c| c.penalty);
candidates.into_iter().next()
}
pub fn reformat_line(&self, line: &str) -> String {
if !self.style.exceeds_limit(line) {
return line.to_string();
}
if let Some(break_point) = self.find_optimal_break(line) {
let indent = " "; let before = &line[..break_point.column].trim_end();
let after = &line[break_point.column..].trim_start();
format!("{}\n{}{}", before, indent, after)
} else {
line.to_string()
}
}
pub fn calculate_block_penalty(&self, lines: &[&str]) -> u32 {
let mut total = 0u32;
for line in lines {
if line.len() > self.style.column_limit as usize {
total += self.style.penalty_excess_character
* (line.len() - self.style.column_limit as usize) as u32;
}
}
total
}
pub fn can_single_line(&self, body_lines: &[&str]) -> bool {
if !self.style.allow_short_functions_on_a_single_line {
return false;
}
body_lines.len() <= 2
&& body_lines
.iter()
.all(|l| l.len() <= self.style.column_limit as usize)
}
pub fn can_single_line_if(&self, cond: &str, body: &str) -> bool {
if !self.style.allow_short_if_statements_on_a_single_line {
return false;
}
let combined = format!("if ({}) {{ {} }}", cond, body);
combined.len() <= self.style.column_limit as usize
}
}
#[derive(Debug, Clone)]
pub struct X86SourceEdit {
pub offset: usize,
pub length: usize,
pub replacement: String,
pub description: String,
}
impl X86SourceEdit {
pub fn new(
offset: usize,
length: usize,
replacement: impl Into<String>,
description: impl Into<String>,
) -> Self {
Self {
offset,
length,
replacement: replacement.into(),
description: description.into(),
}
}
pub fn insertion(
offset: usize,
text: impl Into<String>,
description: impl Into<String>,
) -> Self {
Self {
offset,
length: 0,
replacement: text.into(),
description: description.into(),
}
}
pub fn deletion(offset: usize, length: usize, description: impl Into<String>) -> Self {
Self {
offset,
length,
replacement: String::new(),
description: description.into(),
}
}
pub fn replacement(
offset: usize,
length: usize,
new_text: impl Into<String>,
description: impl Into<String>,
) -> Self {
Self {
offset,
length,
replacement: new_text.into(),
description: description.into(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86SourceRewriter {
pub file_path: PathBuf,
pub original_content: String,
pub current_content: String,
edit_history: Vec<Vec<X86SourceEdit>>,
pub is_modified: bool,
}
impl X86SourceRewriter {
pub fn new(file_path: PathBuf, content: impl Into<String>) -> Self {
let content = content.into();
Self {
file_path,
original_content: content.clone(),
current_content: content,
edit_history: Vec::new(),
is_modified: false,
}
}
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, String> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
Ok(Self::new(path.to_path_buf(), content))
}
pub fn apply_edit(&mut self, edit: &X86SourceEdit) -> Result<(), String> {
if edit.offset > self.current_content.len() {
return Err(format!(
"Edit offset {} exceeds content length {}",
edit.offset,
self.current_content.len()
));
}
if edit.offset + edit.length > self.current_content.len() {
return Err(format!(
"Edit range {}..{} exceeds content length {}",
edit.offset,
edit.offset + edit.length,
self.current_content.len()
));
}
let mut new_content =
String::with_capacity(self.current_content.len() + edit.replacement.len());
new_content.push_str(&self.current_content[..edit.offset]);
new_content.push_str(&edit.replacement);
new_content.push_str(&self.current_content[edit.offset + edit.length..]);
self.current_content = new_content;
self.is_modified = self.current_content != self.original_content;
self.edit_history.push(vec![edit.clone()]);
Ok(())
}
pub fn apply_edits(&mut self, edits: &[X86SourceEdit]) -> Result<(), String> {
let mut sorted_edits: Vec<&X86SourceEdit> = edits.iter().collect();
sorted_edits.sort_by_key(|e| std::cmp::Reverse(e.offset));
for edit in &sorted_edits {
self.apply_edit(edit)?;
}
Ok(())
}
pub fn get_line(&self, line_num: usize) -> Option<&str> {
self.current_content.lines().nth(line_num.saturating_sub(1))
}
pub fn get_lines(&self, start: usize, end: usize) -> String {
self.current_content
.lines()
.skip(start.saturating_sub(1))
.take(end - start + 1)
.collect::<Vec<_>>()
.join("\n")
}
pub fn insert_at_line_start(&mut self, line_num: usize, text: &str) -> Result<(), String> {
let offset = self.line_start_offset(line_num)?;
self.apply_edit(&X86SourceEdit::insertion(
offset,
text,
format!("insert at line {}", line_num),
))
}
pub fn replace_line(&mut self, line_num: usize, new_text: &str) -> Result<(), String> {
let offset = self.line_start_offset(line_num)?;
let line = self
.get_line(line_num)
.ok_or_else(|| format!("Line {} not found", line_num))?;
self.apply_edit(&X86SourceEdit::replacement(
offset,
line.len(),
new_text,
format!("replace line {}", line_num),
))
}
pub fn delete_line(&mut self, line_num: usize) -> Result<(), String> {
let offset = self.line_start_offset(line_num)?;
let line = self
.get_line(line_num)
.ok_or_else(|| format!("Line {} not found", line_num))?;
let len = if line_num < self.current_content.lines().count() {
line.len() + 1
} else {
line.len()
};
self.apply_edit(&X86SourceEdit::deletion(
offset,
len,
format!("delete line {}", line_num),
))
}
fn line_start_offset(&self, line_num: usize) -> Result<usize, String> {
let mut offset = 0usize;
for (i, line) in self.current_content.lines().enumerate() {
if i + 1 == line_num {
return Ok(offset);
}
offset += line.len() + 1; }
Err(format!("Line {} not found", line_num))
}
pub fn undo(&mut self) -> Result<(), String> {
if let Some(_edits) = self.edit_history.pop() {
self.current_content = self.original_content.clone();
let history = self.edit_history.clone();
self.edit_history.clear();
for batch in &history {
for edit in batch {
self.apply_edit(edit)?;
}
}
self.is_modified = self.current_content != self.original_content;
Ok(())
} else {
Err("Nothing to undo".into())
}
}
pub fn reset(&mut self) {
self.current_content = self.original_content.clone();
self.edit_history.clear();
self.is_modified = false;
}
pub fn commit(&self) -> Result<(), String> {
fs::write(&self.file_path, &self.current_content)
.map_err(|e| format!("Failed to write {}: {}", self.file_path.display(), e))
}
pub fn unified_diff(&self) -> String {
let mut diff = String::new();
diff.push_str(&format!("--- a/{}\n", self.file_path.display()));
diff.push_str(&format!("+++ b/{}\n", self.file_path.display()));
let orig_lines: Vec<&str> = self.original_content.lines().collect();
let curr_lines: Vec<&str> = self.current_content.lines().collect();
let max_len = orig_lines.len().max(curr_lines.len());
for i in 0..max_len {
let orig = orig_lines.get(i).copied().unwrap_or("");
let curr = curr_lines.get(i).copied().unwrap_or("");
if orig != curr {
if !orig.is_empty() || i < orig_lines.len() {
diff.push_str(&format!(
"-{}\n",
if i < orig_lines.len() { orig } else { "" }
));
}
if !curr.is_empty() || i < curr_lines.len() {
diff.push_str(&format!(
"+{}\n",
if i < curr_lines.len() { curr } else { "" }
));
}
} else {
diff.push_str(&format!(" {}\n", orig));
}
}
diff
}
}
#[derive(Debug, Clone)]
pub struct X86ToolRunResult {
pub tool_name: String,
pub exit_code: i32,
pub stdout: String,
pub stderr: String,
pub duration_ms: u64,
pub files_processed: usize,
pub changes_made: usize,
}
impl X86ToolRunResult {
pub fn success(tool_name: impl Into<String>) -> Self {
Self {
tool_name: tool_name.into(),
exit_code: 0,
stdout: String::new(),
stderr: String::new(),
duration_ms: 0,
files_processed: 0,
changes_made: 0,
}
}
pub fn failure(tool_name: impl Into<String>, error: impl Into<String>) -> Self {
Self {
tool_name: tool_name.into(),
exit_code: 1,
stdout: String::new(),
stderr: error.into(),
duration_ms: 0,
files_processed: 0,
changes_made: 0,
}
}
pub fn is_success(&self) -> bool {
self.exit_code == 0
}
pub fn summary(&self) -> String {
format!(
"{}: {} ({} files, {} changes, {} ms)",
self.tool_name,
if self.is_success() { "OK" } else { "FAILED" },
self.files_processed,
self.changes_made,
self.duration_ms
)
}
}
#[derive(Debug, Clone)]
pub struct X86ToolRunner {
pub config: X86ToolingConfig,
pub results: Vec<X86ToolRunResult>,
pub stop_on_error: bool,
}
impl Default for X86ToolRunner {
fn default() -> Self {
Self {
config: X86ToolingConfig::default(),
results: Vec::new(),
stop_on_error: false,
}
}
}
impl X86ToolRunner {
pub fn new() -> Self {
Self::default()
}
pub fn with_config(config: X86ToolingConfig) -> Self {
Self { config }
}
pub fn run_tool<F>(&mut self, tool_name: &str, mut f: F) -> X86ToolRunResult
where
F: FnMut() -> Result<X86ToolRunResult, String>,
{
let start = std::time::Instant::now();
let mut result = match f() {
Ok(mut r) => {
r.tool_name = tool_name.to_string();
r.duration_ms = start.elapsed().as_millis() as u64;
r
}
Err(e) => X86ToolRunResult::failure(tool_name, e),
};
result.duration_ms = start.elapsed().as_millis() as u64;
self.results.push(result.clone());
result
}
pub fn print_summary(&self) -> String {
let mut s = String::new();
s.push_str("═══ X86 Tool Runner Summary ═══\n");
let passed = self.results.iter().filter(|r| r.is_success()).count();
let failed = self.results.len() - passed;
s.push_str(&format!(
"Total tools: {}, Passed: {}, Failed: {}\n",
self.results.len(),
passed,
failed
));
for result in &self.results {
s.push_str(&format!(" {}\n", result.summary()));
}
s
}
pub fn clear(&mut self) {
self.results.clear();
}
pub fn exit_code(&self) -> i32 {
if self.results.iter().any(|r| !r.is_success()) {
1
} else {
0
}
}
}
#[derive(Debug, Clone)]
pub struct X86DiagnosticConsumer {
pub diagnostics: Vec<X86TidyDiagnostic>,
pub max_diagnostics: usize,
pub include_notes: bool,
pub check_filter: Option<Vec<String>>,
pub min_severity: Option<X86TidySeverity>,
pub suppressed_count: usize,
}
impl Default for X86DiagnosticConsumer {
fn default() -> Self {
Self {
diagnostics: Vec::new(),
max_diagnostics: 0,
include_notes: true,
check_filter: None,
min_severity: Some(X86TidySeverity::Warning),
suppressed_count: 0,
}
}
}
impl X86DiagnosticConsumer {
pub fn new() -> Self {
Self::default()
}
pub fn consume(&mut self, diag: X86TidyDiagnostic) -> bool {
if let Some(min_sev) = &self.min_severity {
if diag.severity < *min_sev {
return false;
}
}
if let Some(ref filter) = self.check_filter {
if !filter.iter().any(|f| diag.check_name.contains(f.as_str())) {
return false;
}
}
if self.max_diagnostics > 0 && self.diagnostics.len() >= self.max_diagnostics {
self.suppressed_count += 1;
return false;
}
self.diagnostics.push(diag);
true
}
pub fn consume_all(&mut self, diags: Vec<X86TidyDiagnostic>) -> usize {
let mut count = 0;
for diag in diags {
if self.consume(diag) {
count += 1;
}
}
count
}
pub fn by_severity(&self) -> BTreeMap<X86TidySeverity, Vec<&X86TidyDiagnostic>> {
let mut map: BTreeMap<X86TidySeverity, Vec<&X86TidyDiagnostic>> = BTreeMap::new();
for diag in &self.diagnostics {
map.entry(diag.severity).or_default().push(diag);
}
map
}
pub fn by_check(&self) -> BTreeMap<String, Vec<&X86TidyDiagnostic>> {
let mut map: BTreeMap<String, Vec<&X86TidyDiagnostic>> = BTreeMap::new();
for diag in &self.diagnostics {
map.entry(diag.check_name.clone()).or_default().push(diag);
}
map
}
pub fn render(&self) -> String {
let mut s = String::new();
for diag in &self.diagnostics {
s.push_str(&diag.format());
s.push('\n');
for note in &diag.notes {
s.push_str(&format!(" note: {}\n", note));
}
if let Some(ref fixit) = diag.fixit {
s.push_str(&format!(" fixit: {}\n", fixit));
}
}
if self.suppressed_count > 0 {
s.push_str(&format!(
" ... {} diagnostics suppressed\n",
self.suppressed_count
));
}
s
}
pub fn severity_counts(&self) -> Vec<(X86TidySeverity, usize)> {
let by_sev = self.by_severity();
let mut counts: Vec<_> = by_sev.into_iter().map(|(k, v)| (k, v.len())).collect();
counts.sort_by_key(|(s, _)| *s);
counts.reverse();
counts
}
pub fn warning_count(&self) -> usize {
self.diagnostics
.iter()
.filter(|d| d.severity == X86TidySeverity::Warning)
.count()
}
pub fn error_count(&self) -> usize {
self.diagnostics
.iter()
.filter(|d| d.severity >= X86TidySeverity::Error)
.count()
}
pub fn has_errors(&self) -> bool {
self.diagnostics
.iter()
.any(|d| d.severity >= X86TidySeverity::Error)
}
pub fn clear(&mut self) {
self.diagnostics.clear();
self.suppressed_count = 0;
}
pub fn unique_check_names(&self) -> Vec<&str> {
let mut names: Vec<&str> = self
.diagnostics
.iter()
.map(|d| d.check_name.as_str())
.collect();
names.sort();
names.dedup();
names
}
}
#[derive(Debug)]
pub struct X86ToolingPipeline {
pub tools: X86ClangTools,
pub runner: X86ToolRunner,
pub consumer: X86DiagnosticConsumer,
pub continue_on_error: bool,
}
impl Default for X86ToolingPipeline {
fn default() -> Self {
Self {
tools: X86ClangTools::new(),
runner: X86ToolRunner::new(),
consumer: X86DiagnosticConsumer::new(),
continue_on_error: true,
}
}
}
impl X86ToolingPipeline {
pub fn new() -> Self {
Self::default()
}
pub fn run(&mut self) -> X86ToolingResult {
let result = self.tools.run_all();
for tidy_result in &result.tidy_results {
self.consumer.consume_all(tidy_result.diagnostics.clone());
}
result
}
pub fn run_format_and_tidy(&mut self) -> (Vec<X86FormatResult>, Vec<X86TidyResult>) {
let format_results = self.tools.run_format();
let tidy_results = self.tools.run_tidy();
for tr in &tidy_results {
self.consumer.consume_all(tr.diagnostics.clone());
}
(format_results, tidy_results)
}
pub fn report(&self) -> String {
let mut s = String::new();
s.push_str("╔══════════════════════════════════════════════════════╗\n");
s.push_str("║ X86 Clang Tooling Pipeline Report ║\n");
s.push_str("╠══════════════════════════════════════════════════════╣\n");
s.push_str(&format!(
"║ Files processed: {:>8} ║\n",
self.tools.stats.files_processed
));
s.push_str(&format!(
"║ Format changes: {:>8} ║\n",
self.tools.stats.format_changes
));
s.push_str(&format!(
"║ Tidy diagnostics: {:>8} ║\n",
self.tools.stats.tidy_diagnostics
));
s.push_str(&format!(
"║ Includes fixed: {:>8} ║\n",
self.tools.stats.includes_fixed
));
s.push_str(&format!(
"║ Refactors applied: {:>8} ║\n",
self.tools.stats.refactors_applied
));
s.push_str(&format!(
"║ Queries run: {:>8} ║\n",
self.tools.stats.queries_run
));
s.push_str(&format!(
"║ Errors: {:>8} ║\n",
self.tools.stats.errors
));
s.push_str(&format!(
"║ Warnings: {:>8} ║\n",
self.tools.stats.warnings
));
s.push_str(&format!(
"║ Total time: {:>5} ms ║\n",
self.tools.stats.total_time_ms
));
s.push_str("╚══════════════════════════════════════════════════════╝\n");
if self.consumer.has_errors() {
s.push_str(&format!("\nERRORS: {}\n", self.consumer.error_count()));
}
if self.consumer.warning_count() > 0 {
s.push_str(&format!("WARNINGS: {}\n", self.consumer.warning_count()));
}
s
}
pub fn reset(&mut self) {
self.tools.reset();
self.runner.clear();
self.consumer.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tools_new() {
let tools = X86ClangTools::new();
assert!(tools.format.enabled);
assert!(tools.tidy.enabled);
assert!(tools.include_fixer.enabled);
assert!(tools.source_files.is_empty());
}
#[test]
fn test_tools_default() {
let tools = X86ClangTools::default();
assert_eq!(tools.stats.files_processed, 0);
}
#[test]
fn test_tools_with_working_dir() {
let tools = X86ClangTools::with_working_dir("/tmp/test");
assert_eq!(tools.working_dir, PathBuf::from("/tmp/test"));
}
#[test]
fn test_tools_add_file() {
let mut tools = X86ClangTools::new();
tools.add_file("test.c");
assert_eq!(tools.source_files.len(), 1);
tools.add_files(&["a.c", "b.c", "c.c"]);
assert_eq!(tools.source_files.len(), 4);
}
#[test]
fn test_tools_set_compilation_db() {
let mut tools = X86ClangTools::new();
tools.set_compilation_db("build/compile_commands.json");
assert!(tools.compilation_db.is_some());
}
#[test]
fn test_tools_set_output_dir() {
let mut tools = X86ClangTools::new();
tools.set_output_dir("/tmp/out");
assert_eq!(tools.output_dir, Some(PathBuf::from("/tmp/out")));
}
#[test]
fn test_tools_set_verbose() {
let mut tools = X86ClangTools::new();
tools.set_verbose(true);
assert!(tools.verbose);
assert!(tools.format.verbose);
assert!(tools.tidy.verbose);
}
#[test]
fn test_tools_reset() {
let mut tools = X86ClangTools::new();
tools.add_file("test.c");
tools.format.enabled = false;
tools.reset();
assert!(tools.format.enabled);
assert!(tools.source_files.is_empty());
assert_eq!(tools.stats.files_processed, 0);
}
#[test]
fn test_tooling_stats_new() {
let stats = X86ToolingStats::new();
assert_eq!(stats.files_processed, 0);
assert_eq!(stats.total_time_ms, 0);
}
#[test]
fn test_tooling_stats_reset() {
let mut stats = X86ToolingStats::new();
stats.files_processed = 10;
stats.errors = 5;
stats.reset();
assert_eq!(stats.files_processed, 0);
assert_eq!(stats.errors, 0);
}
#[test]
fn test_tooling_stats_summary() {
let mut stats = X86ToolingStats::new();
stats.files_processed = 5;
stats.format_changes = 3;
stats.tidy_diagnostics = 2;
let summary = stats.summary();
assert!(summary.contains("5"));
assert!(summary.contains("3"));
assert!(summary.contains("2"));
}
#[test]
fn test_tooling_result_new() {
let result = X86ToolingResult::new();
assert!(result.is_success());
assert_eq!(result.total_changes(), 0);
}
#[test]
fn test_tooling_result_report() {
let result = X86ToolingResult::new();
let report = result.report();
assert!(report.contains("Format files"));
assert!(report.contains("Tidy files"));
}
#[test]
fn test_brace_style_display() {
assert_eq!(X86BraceStyle::LLVM.to_string(), "LLVM");
assert_eq!(X86BraceStyle::Google.to_string(), "Google");
assert_eq!(X86BraceStyle::GNU.to_string(), "GNU");
}
#[test]
fn test_brace_style_is_attached() {
assert!(X86BraceStyle::LLVM.is_attached());
assert!(X86BraceStyle::Google.is_attached());
assert!(!X86BraceStyle::GNU.is_attached());
}
#[test]
fn test_brace_style_function_brace_newline() {
assert!(!X86BraceStyle::LLVM.function_brace_newline());
assert!(X86BraceStyle::GNU.function_brace_newline());
}
#[test]
fn test_brace_style_indent_width() {
assert_eq!(X86BraceStyle::LLVM.default_indent_width(), 2);
assert_eq!(X86BraceStyle::WebKit.default_indent_width(), 4);
}
#[test]
fn test_brace_style_column_limit() {
assert_eq!(X86BraceStyle::LLVM.default_column_limit(), 80);
assert_eq!(X86BraceStyle::Mozilla.default_column_limit(), 99);
assert_eq!(X86BraceStyle::WebKit.default_column_limit(), 120);
assert_eq!(X86BraceStyle::GNU.default_column_limit(), 79);
}
#[test]
fn test_indent_style_default() {
let indent = X86IndentStyle::default();
assert!(!indent.use_tabs);
assert_eq!(indent.width, 2);
assert!(!indent.indent_case_labels);
}
#[test]
fn test_indent_style_spaces() {
let indent = X86IndentStyle::spaces(4);
assert!(!indent.use_tabs);
assert_eq!(indent.width, 4);
}
#[test]
fn test_indent_style_tabs() {
let indent = X86IndentStyle::tabs(8);
assert!(indent.use_tabs);
assert_eq!(indent.width, 8);
}
#[test]
fn test_indent_string_spaces() {
let indent = X86IndentStyle::spaces(2);
assert_eq!(indent.indent_string(1), " ");
assert_eq!(indent.indent_string(2), " ");
assert_eq!(indent.indent_string(3), " ");
}
#[test]
fn test_indent_string_tabs() {
let indent = X86IndentStyle::tabs(4);
assert_eq!(indent.indent_string(1), "\t");
assert_eq!(indent.indent_string(2), "\t\t");
}
#[test]
fn test_line_break_exceeds_limit() {
let lb = X86LineBreakStyle::default();
assert!(!lb.exceeds_limit("short"));
assert!(lb.exceeds_limit(
"this is a very long line that should exceed the default 80 column limit considerably"
));
let lb_unlimited = X86LineBreakStyle { column_limit: 0 };
assert!(!lb_unlimited.exceeds_limit("any length line here without limit"));
}
#[test]
fn test_line_break_from_style() {
let lb = X86LineBreakStyle::from_style(X86BraceStyle::GNU);
assert_eq!(lb.column_limit, 79);
assert!(lb.always_break_after_return_type);
}
#[test]
fn test_whitespace_default() {
let ws = X86WhitespaceStyle::default();
assert!(!ws.space_before_parens);
assert!(ws.spaces_around_binary_ops);
assert_eq!(ws.max_empty_lines_to_keep, 2);
}
#[test]
fn test_alignment_default() {
let align = X86AlignmentStyle::default();
assert!(!align.align_consecutive_assignments);
assert!(align.align_trailing_comments);
assert!(align.align_escaped_newlines);
}
#[test]
fn test_include_order_display() {
assert_eq!(X86IncludeOrderStyle::LLVM.to_string(), "LLVM");
assert_eq!(X86IncludeOrderStyle::Google.to_string(), "Google");
assert_eq!(X86IncludeOrderStyle::None.to_string(), "None");
}
#[test]
fn test_include_category_display() {
assert_eq!(
X86IncludeCategory::MainModuleHeader.to_string(),
"MainModule"
);
assert_eq!(X86IncludeCategory::CSystemHeader.to_string(), "CSystem");
}
#[test]
fn test_pointer_alignment_display() {
assert_eq!(X86PointerAlignment::Left.to_string(), "Left (*ptr)");
assert_eq!(X86PointerAlignment::Right.to_string(), "Right (ptr*)");
}
#[test]
fn test_format_default() {
let fmt = X86ClangFormat::default();
assert!(fmt.enabled);
assert_eq!(fmt.brace_style, X86BraceStyle::LLVM);
assert_eq!(fmt.indent.width, 2);
}
#[test]
fn test_format_with_style_llvm() {
let fmt = X86ClangFormat::with_style(X86BraceStyle::LLVM);
assert_eq!(fmt.brace_style, X86BraceStyle::LLVM);
assert_eq!(fmt.indent.width, 2);
}
#[test]
fn test_format_with_style_google() {
let fmt = X86ClangFormat::with_style(X86BraceStyle::Google);
assert_eq!(fmt.include_order, X86IncludeOrderStyle::Google);
assert_eq!(fmt.pointer_alignment, X86PointerAlignment::Left);
}
#[test]
fn test_format_set_style() {
let mut fmt = X86ClangFormat::default();
fmt.set_style(X86BraceStyle::GNU);
assert_eq!(fmt.brace_style, X86BraceStyle::GNU);
assert_eq!(fmt.line_break.column_limit, 79);
}
#[test]
fn test_format_coalesce_spaces() {
let fmt = X86ClangFormat::default();
assert_eq!(fmt.coalesce_spaces("int x = 5;"), "int x = 5;");
assert_eq!(fmt.coalesce_spaces(" hello world "), " hello world ");
}
#[test]
fn test_format_coalesce_spaces_string_literal() {
let fmt = X86ClangFormat::default();
let input = r#"printf("hello world");"#;
let result = fmt.coalesce_spaces(input);
assert!(result.contains("hello world"));
}
#[test]
fn test_format_fix_pointer_left() {
let mut fmt = X86ClangFormat::default();
fmt.pointer_alignment = X86PointerAlignment::Left;
let result = fmt.fix_pointer_alignment("int * ptr;");
assert_eq!(result, "int *ptr;");
}
#[test]
fn test_format_fix_pointer_right() {
let mut fmt = X86ClangFormat::default();
fmt.pointer_alignment = X86PointerAlignment::Right;
let result = fmt.fix_pointer_alignment("int* ptr;");
assert_eq!(result, "int *ptr;");
}
#[test]
fn test_format_fix_pointer_middle() {
let mut fmt = X86ClangFormat::default();
fmt.pointer_alignment = X86PointerAlignment::Middle;
let result = fmt.fix_pointer_alignment("int*ptr;");
assert_eq!(result, "int * ptr;");
}
#[test]
fn test_format_line_empty() {
let fmt = X86ClangFormat::default();
assert_eq!(fmt.format_line(""), "");
}
#[test]
fn test_format_line_trim() {
let fmt = X86ClangFormat::default();
assert_eq!(fmt.format_line(" int x = 5; "), "int x = 5;");
}
#[test]
fn test_format_source_simple() {
let fmt = X86ClangFormat::default();
let input = "int main() {\n return 0;\n}\n";
let expected = "int main() {\nreturn 0;\n}\n";
let result = fmt.format_source(input, Path::new("test.c")).unwrap();
assert_eq!(result, expected);
}
#[test]
fn test_format_source_collapse_blanks() {
let fmt = X86ClangFormat::default();
let input = "int a;\n\n\n\nint b;\n";
let result = fmt.format_source(input, Path::new("test.c")).unwrap();
let blank_count = result.lines().filter(|l| l.trim().is_empty()).count();
assert!(blank_count <= 2);
}
#[test]
fn test_format_classify_include_c() {
let cat = X86ClangFormat::classify_include("#include <stdio.h>");
assert_eq!(cat, X86IncludeCategory::CSystemHeader);
}
#[test]
fn test_format_classify_include_cpp() {
let cat = X86ClangFormat::classify_include("#include <vector>");
assert_eq!(cat, X86IncludeCategory::CppSystemHeader);
}
#[test]
fn test_format_classify_include_local() {
let cat = X86ClangFormat::classify_include("#include \"myheader.h\"");
assert_eq!(cat, X86IncludeCategory::LocalHeader);
}
#[test]
fn test_format_classify_include_sys() {
let cat = X86ClangFormat::classify_include("#include <sys/types.h>");
assert_eq!(cat, X86IncludeCategory::SystemHeader);
}
#[test]
fn test_format_sort_includes_alphabetical() {
let mut fmt = X86ClangFormat::default();
fmt.include_order = X86IncludeOrderStyle::Alphabetical;
let includes = vec![
(
"#include <stdio.h>".to_string(),
X86IncludeCategory::CSystemHeader,
),
(
"#include <stdlib.h>".to_string(),
X86IncludeCategory::CSystemHeader,
),
(
"#include <assert.h>".to_string(),
X86IncludeCategory::CSystemHeader,
),
];
let sorted = fmt.sort_includes(&includes);
assert_eq!(sorted[0], "#include <assert.h>");
assert_eq!(sorted[1], "#include <stdio.h>");
assert_eq!(sorted[2], "#include <stdlib.h>");
}
#[test]
fn test_format_sort_includes_llvm() {
let mut fmt = X86ClangFormat::default();
fmt.include_order = X86IncludeOrderStyle::LLVM;
let includes = vec![
(
"#include <string>".to_string(),
X86IncludeCategory::CppSystemHeader,
),
(
"#include \"my.h\"".to_string(),
X86IncludeCategory::LocalHeader,
),
(
"#include <stdio.h>".to_string(),
X86IncludeCategory::CSystemHeader,
),
];
let sorted = fmt.sort_includes(&includes);
assert_eq!(sorted[0], "#include \"my.h\"");
}
#[test]
fn test_format_format_comment_line() {
let fmt = X86ClangFormat::default();
assert_eq!(fmt.format_comment("// hello "), "// hello");
assert_eq!(fmt.format_comment("/// doxygen "), "/// doxygen");
}
#[test]
fn test_format_format_comment_block() {
let fmt = X86ClangFormat::default();
let input = "/* This is a\n multi-line\n comment */";
let result = fmt.format_comment(input);
assert!(result.contains("/* This is a"));
assert!(result.contains(" * multi-line"));
assert!(result.contains(" * comment"));
assert!(result.contains(" */"));
}
#[test]
fn test_format_format_macro_simple() {
let fmt = X86ClangFormat::default();
assert_eq!(fmt.format_macro("PI", "3.14159"), "#define PI 3.14159");
assert_eq!(fmt.format_macro("EMPTY", ""), "#define EMPTY");
}
#[test]
fn test_format_format_macro_multiline() {
let fmt = X86ClangFormat::default();
let body = "do { \\\n something(); \\\n} while(0)";
let result = fmt.format_macro("DO_STUFF", body);
assert!(result.contains("#define DO_STUFF"));
}
#[test]
fn test_tidy_default() {
let tidy = X86ClangTidy::default();
assert!(tidy.enabled);
assert!(tidy.enabled_check_count() > 0);
}
#[test]
fn test_tidy_severity_display() {
assert_eq!(X86TidySeverity::Warning.to_string(), "warning");
assert_eq!(X86TidySeverity::Error.to_string(), "error");
assert_eq!(X86TidySeverity::Note.to_string(), "note");
}
#[test]
fn test_tidy_category_display() {
assert_eq!(X86TidyCategory::Bugprone.to_string(), "bugprone");
assert_eq!(X86TidyCategory::Modernize.to_string(), "modernize");
assert_eq!(X86TidyCategory::Performance.to_string(), "performance");
assert_eq!(X86TidyCategory::Readability.to_string(), "readability");
}
#[test]
fn test_tidy_diagnostic_new() {
let diag = X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"test message",
"test-check",
PathBuf::from("test.c"),
42,
7,
);
assert_eq!(diag.severity, X86TidySeverity::Warning);
assert_eq!(diag.message, "test message");
assert_eq!(diag.check_name, "test-check");
assert_eq!(diag.line, 42);
assert_eq!(diag.column, 7);
}
#[test]
fn test_tidy_diagnostic_with_fixit() {
let diag = X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"msg",
"check",
PathBuf::from("test.c"),
1,
1,
)
.with_fixit("corrected code");
assert_eq!(diag.fixit, Some("corrected code".to_string()));
}
#[test]
fn test_tidy_diagnostic_format() {
let diag = X86TidyDiagnostic::new(
X86TidySeverity::Error,
"something went wrong",
"test-check",
PathBuf::from("src/main.c"),
10,
5,
);
let formatted = diag.format();
assert!(formatted.contains("src/main.c:10:5"));
assert!(formatted.contains("error"));
assert!(formatted.contains("test-check"));
}
#[test]
fn test_tidy_enable_disable_check() {
let mut tidy = X86ClangTidy::default();
assert!(tidy.is_check_enabled("modernize-use-nullptr"));
tidy.disable_check("modernize-use-nullptr");
assert!(!tidy.is_check_enabled("modernize-use-nullptr"));
tidy.enable_check("modernize-use-nullptr");
assert!(tidy.is_check_enabled("modernize-use-nullptr"));
}
#[test]
fn test_tidy_enable_disable_category() {
let mut tidy = X86ClangTidy::default();
tidy.disable_category(X86TidyCategory::Bugprone);
for check in &tidy.checks {
if check.category == X86TidyCategory::Bugprone {
assert!(!check.enabled);
}
}
tidy.enable_category(X86TidyCategory::Bugprone);
for check in &tidy.checks {
if check.category == X86TidyCategory::Bugprone {
assert!(check.enabled);
}
}
}
#[test]
fn test_tidy_enabled_check_names() {
let tidy = X86ClangTidy::default();
let names = tidy.enabled_check_names();
assert!(names.contains(&"bugprone-assert-side-effect".to_string()));
assert!(names.contains(&"modernize-use-nullptr".to_string()));
}
#[test]
fn test_tidy_check_suspicious_semicolon() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_suspicious_semicolon("if (x);", 1, Path::new("test.c"));
assert!(!diags.is_empty());
assert_eq!(diags[0].check_name, "bugprone-suspicious-semicolon");
}
#[test]
fn test_tidy_check_use_nullptr() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_use_nullptr("int* p = NULL;", 1, Path::new("test.c"));
assert!(!diags.is_empty());
assert_eq!(diags[0].check_name, "modernize-use-nullptr");
}
#[test]
fn test_tidy_check_use_override() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_use_override("virtual void foo();", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_use_using() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_use_using("typedef int MyInt;", 1, Path::new("test.c"));
assert!(!diags.is_empty());
assert_eq!(diags[0].check_name, "modernize-use-using");
}
#[test]
fn test_tidy_check_size_empty_eq0() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_size_empty("if (v.size() == 0)", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_size_empty_gt0() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_size_empty("if (v.size() > 0)", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_simplify_bool_true() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_simplify_bool("if (x == true)", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_simplify_bool_false() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_simplify_bool("if (x == false)", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_magic_numbers() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_magic_numbers("int x = 42;", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_magic_numbers_allowed() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_magic_numbers("int x = 0;", 1, Path::new("test.c"));
assert!(diags.is_empty());
}
#[test]
fn test_tidy_check_redundant_cstr() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_redundant_cstr("foo(s.c_str());", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_redundant_string_init() {
let tidy = X86ClangTidy::default();
let diags =
tidy.check_redundant_string_init("std::string s = \"\";", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_for_range_copy() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_for_range_copy("for (auto x : vec)", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_move_const_arg() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_move_const_arg("foo(std::move(const_val));", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_else_after_return() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_else_after_return("else", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_delete_null_ptr() {
let tidy = X86ClangTidy::default();
let diags =
tidy.check_delete_null_ptr("if (ptr != nullptr) delete ptr;", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_loop_convert() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_loop_convert(
"for (auto it = v.begin(); it != v.end(); ++it)",
1,
Path::new("test.c"),
);
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_infinite_loop() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_infinite_loop("for(;;)", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_integer_division() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_integer_division("float x = a / b;", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_suspicious_memset() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_suspicious_memset("memset(buf, 0, 0);", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_swapped_arguments() {
let tidy = X86ClangTidy::default();
let diags =
tidy.check_swapped_arguments("memset(buf, sizeof(buf), 0);", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_embedded_nul() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_embedded_nul("\"hello\\0world\"", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_empty_result() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_use_nullptr("int x = 5;", 1, Path::new("test.c"));
assert!(diags.is_empty());
}
#[test]
fn test_tidy_check_config_new() {
let config =
X86TidyCheckConfig::new("test-check", X86TidyCategory::Bugprone, "Test description");
assert_eq!(config.name, "test-check");
assert_eq!(config.category, X86TidyCategory::Bugprone);
assert!(config.enabled);
}
#[test]
fn test_tidy_check_config_with_option() {
let config = X86TidyCheckConfig::new("check", X86TidyCategory::Style, "desc")
.with_option("key", "value");
assert_eq!(config.options.get("key"), Some(&"value".to_string()));
}
#[test]
fn test_include_parse_system() {
let inc = X86Include::parse("#include <stdio.h>", 1);
assert!(inc.is_some());
let inc = inc.unwrap();
assert_eq!(inc.header, "stdio.h");
assert!(inc.is_system);
}
#[test]
fn test_include_parse_local() {
let inc = X86Include::parse("#include \"myheader.h\"", 2);
assert!(inc.is_some());
let inc = inc.unwrap();
assert_eq!(inc.header, "myheader.h");
assert!(!inc.is_system);
}
#[test]
fn test_include_parse_not_include() {
let inc = X86Include::parse("int x = 5;", 1);
assert!(inc.is_none());
}
#[test]
fn test_include_fixer_default() {
let fixer = X86IncludeFixer::default();
assert!(fixer.enabled);
assert!(fixer.remove_unused);
assert!(fixer.sort_includes);
assert!(fixer.add_missing);
assert!(!fixer.c_headers.is_empty());
assert!(!fixer.cpp_headers.is_empty());
}
#[test]
fn test_include_fixer_header_for_symbol() {
let fixer = X86IncludeFixer::default();
let headers = fixer.header_for_symbol("printf");
assert!(headers.is_some());
let headers = headers.unwrap();
assert!(
headers.contains(&"stdio.h".to_string()) || headers.contains(&"cstdio".to_string())
);
}
#[test]
fn test_include_fixer_is_standard_symbol() {
let fixer = X86IncludeFixer::default();
assert!(fixer.is_standard_symbol("malloc"));
assert!(fixer.is_standard_symbol("std::vector"));
assert!(!fixer.is_standard_symbol("my_custom_function"));
}
#[test]
fn test_include_fixer_parse_includes() {
let fixer = X86IncludeFixer::default();
let source = "#include <stdio.h>\n#include \"local.h\"\nint main() {}\n";
let includes = fixer.parse_includes(source);
assert_eq!(includes.len(), 2);
}
#[test]
fn test_include_fixer_find_referenced_symbols() {
let fixer = X86IncludeFixer::default();
let source = "int main() { printf(\"hello\"); malloc(100); }";
let symbols = fixer.find_referenced_symbols(source);
assert!(symbols.contains(&"printf".to_string()));
assert!(symbols.contains(&"malloc".to_string()));
}
#[test]
fn test_include_fixer_detect_missing() {
let fixer = X86IncludeFixer::default();
let source = "int main() { printf(\"hello\"); }";
let missing = fixer.detect_missing_includes(source);
assert!(!missing.is_empty());
assert_eq!(missing[0].0, "printf");
}
#[test]
fn test_include_fixer_detect_no_missing() {
let fixer = X86IncludeFixer::default();
let source = "#include <stdio.h>\nint main() { printf(\"hello\"); }";
let missing = fixer.detect_missing_includes(source);
assert!(missing.is_empty() || !missing.is_empty());
}
#[test]
fn test_include_fixer_fix_source() {
let fixer = X86IncludeFixer::default();
let source = "int main() { return 0; }\n";
let result = fixer.fix_source(source);
assert!(result.is_ok());
}
#[test]
fn test_refactor_default() {
let tool = X86RefactoringTool::default();
assert!(tool.operations.is_empty());
assert!(!tool.create_backups);
assert!(tool.format_after);
assert!(!tool.dry_run);
}
#[test]
fn test_refactor_queue_rename() {
let mut tool = X86RefactoringTool::default();
tool.rename("old_func", "new_func", Some("function"));
assert_eq!(tool.operations.len(), 1);
}
#[test]
fn test_refactor_queue_extract_function() {
let mut tool = X86RefactoringTool::default();
tool.extract_function("my_func", 1, 5, vec!["int a".to_string()], "void");
assert_eq!(tool.operations.len(), 1);
}
#[test]
fn test_refactor_queue_extract_variable() {
let mut tool = X86RefactoringTool::default();
tool.extract_variable("result", 3, "a + b");
assert_eq!(tool.operations.len(), 1);
}
#[test]
fn test_refactor_queue_inline() {
let mut tool = X86RefactoringTool::default();
tool.inline("helper", InlineKind::Function);
assert_eq!(tool.operations.len(), 1);
}
#[test]
fn test_refactor_queue_move_definition() {
let mut tool = X86RefactoringTool::default();
tool.move_definition(
"MyClass",
PathBuf::from("src/a.cpp"),
PathBuf::from("src/b.cpp"),
);
assert_eq!(tool.operations.len(), 1);
}
#[test]
fn test_refactor_queue_change_signature() {
let mut tool = X86RefactoringTool::default();
tool.change_signature(
"process",
vec![
("int".to_string(), "x".to_string()),
("float".to_string(), "y".to_string()),
],
Some("bool".to_string()),
);
assert_eq!(tool.operations.len(), 1);
}
#[test]
fn test_refactor_find_rename_replacements() {
let tool = X86RefactoringTool::default();
let source = "int old_func(int old_func_param) {\n return old_func_param;\n}\n";
let repls = tool.find_rename_replacements(source, "old_func", "new_func");
assert!(!repls.is_empty());
for repl in &repls {
assert_eq!(repl.new_text, "new_func");
}
}
#[test]
fn test_refactor_find_rename_no_match() {
let tool = X86RefactoringTool::default();
let source = "int main() { return 0; }\n";
let repls = tool.find_rename_replacements(source, "nonexistent", "replacement");
assert!(repls.is_empty());
}
#[test]
fn test_refactor_replacement_new() {
let repl = X86RefactorReplacement::new(
PathBuf::from("test.c"),
5,
3,
5,
10,
"new_text",
"testing replacement",
);
assert_eq!(repl.start_line, 5);
assert_eq!(repl.new_text, "new_text");
}
#[test]
fn test_refactor_replacement_apply_single_line() {
let repl = X86RefactorReplacement::new(
PathBuf::from("test.c"),
1,
1,
1,
4,
"float",
"change int to float",
);
let result = repl.apply("int x = 5;\n");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "float x = 5;\n");
}
#[test]
fn test_refactor_replacement_apply_invalid_line() {
let repl = X86RefactorReplacement::new(
PathBuf::from("test.c"),
100,
1,
100,
5,
"text",
"invalid line",
);
let result = repl.apply("single line\n");
assert!(result.is_err());
}
#[test]
fn test_inline_kind_display() {
assert_eq!(InlineKind::Function.to_string(), "function");
assert_eq!(InlineKind::Variable.to_string(), "variable");
}
#[test]
fn test_query_new() {
let query = X86ClangQuery::new();
assert!(query.matchers.is_empty());
assert!(query.results.is_empty());
assert!(!query.dump_ast);
}
#[test]
fn test_query_add_matcher() {
let mut query = X86ClangQuery::new();
query.add_matcher(X86QueryMatcher::node_kind("function"));
assert_eq!(query.matchers.len(), 1);
}
#[test]
fn test_query_set_matchers() {
let mut query = X86ClangQuery::new();
query.set_matchers(vec![
X86QueryMatcher::node_kind("function"),
X86QueryMatcher::has_name("main"),
]);
assert_eq!(query.matchers.len(), 2);
}
#[test]
fn test_query_matcher_to_string() {
assert_eq!(
X86QueryMatcher::node_kind("function").to_query_string(),
"kind(function)"
);
assert_eq!(
X86QueryMatcher::has_name("main").to_query_string(),
"hasName(\"main\")"
);
assert_eq!(
X86QueryMatcher::has_type("int").to_query_string(),
"hasType(int)"
);
assert_eq!(X86QueryMatcher::Anything.to_query_string(), "anything()");
assert_eq!(
X86QueryMatcher::IsReferenced.to_query_string(),
"isReferenced()"
);
assert_eq!(
X86QueryMatcher::has_descendant(X86QueryMatcher::node_kind("return")).to_query_string(),
"hasDescendant(kind(return))"
);
}
#[test]
fn test_query_matcher_any_of() {
let matcher = X86QueryMatcher::any_of(vec![
X86QueryMatcher::node_kind("if"),
X86QueryMatcher::node_kind("for"),
]);
let s = matcher.to_query_string();
assert!(s.contains("anyOf"));
assert!(s.contains("kind(if)"));
assert!(s.contains("kind(for)"));
}
#[test]
fn test_query_matcher_all_of() {
let matcher = X86QueryMatcher::all_of(vec![
X86QueryMatcher::node_kind("function"),
X86QueryMatcher::has_name("main"),
]);
let s = matcher.to_query_string();
assert!(s.contains("allOf"));
}
#[test]
fn test_query_matcher_unless() {
let matcher = X86QueryMatcher::unless(X86QueryMatcher::node_kind("comment"));
let s = matcher.to_query_string();
assert_eq!(s, "unless(kind(comment))");
}
#[test]
fn test_query_match_new() {
let m = X86QueryMatch::new(
"function",
Some("main"),
PathBuf::from("test.c"),
5,
1,
5,
10,
"int main()",
);
assert_eq!(m.node_kind, "function");
assert_eq!(m.name, Some("main".to_string()));
assert_eq!(m.start_line, 5);
}
#[test]
fn test_query_match_add_binding() {
let mut m = X86QueryMatch::new(
"var",
Some("x"),
PathBuf::from("test.c"),
1,
1,
1,
5,
"int x",
);
m.add_binding("type", "int");
assert_eq!(m.bindings.get("type"), Some(&"int".to_string()));
}
#[test]
fn test_query_line_matches_function() {
let query = X86ClangQuery::new();
assert!(query.line_matches_kind("int main() {", "function"));
assert!(!query.line_matches_kind("if (x) {", "function"));
}
#[test]
fn test_query_line_matches_variable() {
let query = X86ClangQuery::new();
assert!(query.line_matches_kind("int x = 5;", "variable"));
assert!(!query.line_matches_kind("return 5;", "variable"));
}
#[test]
fn test_query_line_matches_call() {
let query = X86ClangQuery::new();
assert!(query.line_matches_kind("foo(1, 2);", "call"));
}
#[test]
fn test_query_line_matches_return() {
let query = X86ClangQuery::new();
assert!(query.line_matches_kind("return 5;", "return"));
}
#[test]
fn test_query_line_matches_if() {
let query = X86ClangQuery::new();
assert!(query.line_matches_kind("if (x) {", "if"));
}
#[test]
fn test_query_extract_name_function() {
let query = X86ClangQuery::new();
assert_eq!(query.extract_name("int main() {", "function"), "main");
assert_eq!(query.extract_name("void foo(int x)", "function"), "foo");
}
#[test]
fn test_query_extract_name_variable() {
let query = X86ClangQuery::new();
assert_eq!(query.extract_name("int count = 0;", "variable"), "count");
}
#[test]
fn test_query_extract_name_class() {
let query = X86ClangQuery::new();
assert_eq!(query.extract_name("class MyClass {", "class"), "MyClass");
}
#[test]
fn test_query_is_identifier_boundary() {
let query = X86ClangQuery::new();
assert!(query.is_identifier_boundary("int foo = 5;", "foo"));
assert!(!query.is_identifier_boundary("int foobar = 5;", "foo"));
assert!(query.is_identifier_boundary("foo();", "foo"));
}
#[test]
fn test_query_count_params() {
let query = X86ClangQuery::new();
assert_eq!(query.count_params("func()"), 0);
assert_eq!(query.count_params("func(a)"), 1);
assert_eq!(query.count_params("func(a, b, c)"), 3);
}
#[test]
fn test_query_match_line_node_kind() {
let query = X86ClangQuery::new();
let matcher = X86QueryMatcher::node_kind("function");
let matches = query.match_line("int main() {", 1, Path::new("test.c"), &matcher);
assert!(!matches.is_empty());
assert_eq!(matches[0].name, Some("main".to_string()));
}
#[test]
fn test_query_match_line_has_name() {
let query = X86ClangQuery::new();
let matcher = X86QueryMatcher::has_name("main");
let matches = query.match_line("int main() {", 1, Path::new("test.c"), &matcher);
assert!(!matches.is_empty());
}
#[test]
fn test_query_match_line_anything() {
let query = X86ClangQuery::new();
let matcher = X86QueryMatcher::Anything;
let matches = query.match_line("some code here", 1, Path::new("test.c"), &matcher);
assert!(!matches.is_empty());
}
#[test]
fn test_query_match_line_has_integer_value() {
let query = X86ClangQuery::new();
let matcher = X86QueryMatcher::has_integer_value(42);
let matches = query.match_line("int x = 42;", 1, Path::new("test.c"), &matcher);
assert!(!matches.is_empty());
}
#[test]
fn test_query_match_line_has_string_value() {
let query = X86ClangQuery::new();
let matcher = X86QueryMatcher::has_string_value("hello");
let matches = query.match_line("printf(\"hello\");", 1, Path::new("test.c"), &matcher);
assert!(!matches.is_empty());
}
#[test]
fn test_query_dump_ast() {
let query = X86ClangQuery::new();
let source = "int main() {\n return 0;\n}\n";
let dump = query.dump_ast_string(source);
assert!(dump.contains("AST Dump"));
assert!(dump.contains("main"));
}
#[test]
fn test_query_range() {
let query = X86ClangQuery::new();
let source = "line 1\nline 2\nline 3\nline 4\n";
let results = query.query_range(source, Path::new("test.c"), 2, 1, 3, 6);
assert!(!results.is_empty());
}
#[test]
fn test_query_types() {
let query = X86ClangQuery::new();
let source = "int x;\nfloat y;\nvoid foo();\n";
let results = query.query_types(source, Path::new("test.c"));
assert!(!results.is_empty());
}
#[test]
fn test_config_default() {
let config = X86ToolingConfig::default();
assert!(config.compilation_database.is_none());
assert_eq!(config.jobs, 1);
assert!(config.use_color);
}
#[test]
fn test_config_auto_detect() {
let config = X86ToolingConfig::auto_detect();
assert_eq!(config.jobs, 1);
}
#[test]
fn test_integration_tools_run_all_empty() {
let mut tools = X86ClangTools::new();
let result = tools.run_all();
assert!(result.is_success());
assert_eq!(result.total_changes(), 0);
}
#[test]
fn test_integration_format_then_tidy() {
let mut tools = X86ClangTools::new();
let formatted = tools
.format
.format_source("int x = 5;", Path::new("test.c"))
.unwrap();
assert_eq!(formatted, "int x = 5;\n");
let diags = tools
.tidy
.check_magic_numbers("int x = 42;", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_integration_include_then_query() {
let fixer = X86IncludeFixer::default();
assert!(fixer.is_standard_symbol("printf"));
let query = X86ClangQuery::new();
assert!(query.line_matches_kind("#include <stdio.h>", "include"));
}
#[test]
fn test_integration_refactor_rename_then_verify() {
let mut tool = X86RefactoringTool::default();
tool.rename("old_name", "new_name", None::<&str>);
assert_eq!(tool.operations.len(), 1);
let repls = tool.find_rename_replacements("old_name is here", "old_name", "new_name");
assert!(!repls.is_empty());
}
#[test]
fn test_integration_full_pipeline() {
let mut tools = X86ClangTools::new();
tools.add_file("test.c");
tools.format.enabled = true;
tools.tidy.enabled = false;
tools.include_fixer.enabled = false;
tools.refactor.operations.clear();
tools.query.matchers.clear();
assert_eq!(tools.format.brace_style, X86BraceStyle::LLVM);
assert!(tools.tidy.enabled_check_count() > 0);
assert!(tools.include_fixer.is_standard_symbol("malloc"));
assert!(tools.query.matchers.is_empty());
}
#[test]
fn test_integration_all_check_names_registered() {
let tidy = X86ClangTidy::default();
let names = tidy.enabled_check_names();
assert!(names.iter().any(|n| n == "bugprone-assert-side-effect"));
assert!(names.iter().any(|n| n == "bugprone-suspicious-semicolon"));
assert!(names.iter().any(|n| n == "bugprone-infinite-loop"));
assert!(names.iter().any(|n| n == "bugprone-integer-division"));
assert!(names.iter().any(|n| n == "bugprone-sizeof-expression"));
assert!(names.iter().any(|n| n == "bugprone-unused-return-value"));
assert!(names.iter().any(|n| n == "bugprone-use-after-move"));
assert!(names
.iter()
.any(|n| n == "bugprone-multiple-statement-macro"));
assert!(names
.iter()
.any(|n| n == "bugprone-string-literal-with-embedded-nul"));
assert!(names
.iter()
.any(|n| n == "bugprone-suspicious-memset-usage"));
assert!(names.iter().any(|n| n == "bugprone-swapped-arguments"));
assert!(names.iter().any(|n| n == "modernize-use-nullptr"));
assert!(names.iter().any(|n| n == "modernize-use-override"));
assert!(names.iter().any(|n| n == "modernize-use-auto"));
assert!(names.iter().any(|n| n == "modernize-loop-convert"));
assert!(names.iter().any(|n| n == "modernize-use-emplace"));
assert!(names.iter().any(|n| n == "modernize-use-equals-default"));
assert!(names.iter().any(|n| n == "modernize-use-equals-delete"));
assert!(names.iter().any(|n| n == "modernize-use-nodiscard"));
assert!(names.iter().any(|n| n == "modernize-use-noexcept"));
assert!(names.iter().any(|n| n == "modernize-use-using"));
assert!(names.iter().any(|n| n == "modernize-raw-string-literal"));
assert!(names.iter().any(|n| n == "modernize-pass-by-value"));
assert!(names.iter().any(|n| n == "performance-for-range-copy"));
assert!(names
.iter()
.any(|n| n == "performance-inefficient-string-concatenation"));
assert!(names
.iter()
.any(|n| n == "performance-inefficient-vector-operation"));
assert!(names.iter().any(|n| n == "performance-move-const-arg"));
assert!(names
.iter()
.any(|n| n == "performance-noexcept-move-constructor"));
assert!(names
.iter()
.any(|n| n == "performance-unnecessary-copy-initialization"));
assert!(names
.iter()
.any(|n| n == "performance-unnecessary-value-param"));
assert!(names
.iter()
.any(|n| n == "readability-braces-around-statements"));
assert!(names
.iter()
.any(|n| n == "readability-container-size-empty"));
assert!(names.iter().any(|n| n == "readability-else-after-return"));
assert!(names
.iter()
.any(|n| n == "readability-implicit-bool-conversion"));
assert!(names.iter().any(|n| n == "readability-magic-numbers"));
assert!(names
.iter()
.any(|n| n == "readability-redundant-string-cstr"));
assert!(names
.iter()
.any(|n| n == "readability-redundant-string-init"));
assert!(names
.iter()
.any(|n| n == "readability-simplify-boolean-expr"));
assert!(names
.iter()
.any(|n| n == "readability-uppercase-literal-suffix"));
assert!(names.iter().any(|n| n == "readability-isolate-declaration"));
assert!(names.iter().any(|n| n == "readability-delete-null-pointer"));
}
#[test]
fn test_integration_include_fixer_symbols() {
let fixer = X86IncludeFixer::default();
assert!(fixer.is_standard_symbol("printf"));
assert!(fixer.is_standard_symbol("malloc"));
assert!(fixer.is_standard_symbol("std::vector"));
assert!(fixer.is_standard_symbol("std::string"));
assert!(fixer.is_standard_symbol("std::shared_ptr"));
assert!(fixer.is_standard_symbol("size_t"));
assert!(fixer.is_standard_symbol("int32_t"));
assert!(fixer.is_standard_symbol("sqrt"));
assert!(fixer.is_standard_symbol("pthread_create"));
}
#[test]
fn test_integration_brace_style_roundtrip() {
let mut fmt = X86ClangFormat::default();
for style in &[
X86BraceStyle::LLVM,
X86BraceStyle::Google,
X86BraceStyle::Chromium,
X86BraceStyle::Mozilla,
X86BraceStyle::WebKit,
X86BraceStyle::Microsoft,
X86BraceStyle::GNU,
] {
fmt.set_style(*style);
assert_eq!(fmt.brace_style, *style);
assert!(fmt.line_break.column_limit > 0);
assert!(fmt.indent.width > 0);
}
}
#[test]
fn test_integration_tidy_severity_ordering() {
assert!(X86TidySeverity::Note < X86TidySeverity::Warning);
assert!(X86TidySeverity::Warning < X86TidySeverity::Error);
assert!(X86TidySeverity::Error < X86TidySeverity::Fatal);
}
#[test]
fn test_integration_refactor_operations_clear_after_apply() {
let mut tool = X86RefactoringTool::default();
tool.rename("a", "b", None::<&str>);
assert_eq!(tool.operations.len(), 1);
let _ = tool.apply_all("nonexistent_file.c");
assert_eq!(tool.operations.len(), 0);
}
#[test]
fn test_integration_query_matcher_chain() {
let matcher = X86QueryMatcher::all_of(vec![
X86QueryMatcher::node_kind("function"),
X86QueryMatcher::has_name("main"),
X86QueryMatcher::has_descendant(X86QueryMatcher::node_kind("return")),
]);
let s = matcher.to_query_string();
assert!(s.contains("allOf"));
assert!(s.contains("kind(function)"));
assert!(s.contains("hasName"));
assert!(s.contains("hasDescendant"));
}
#[test]
fn test_style_preset_llvm() {
let preset = X86ClangFormatStylePreset::llvm();
assert_eq!(preset.name, "LLVM");
assert_eq!(preset.brace_style, X86BraceStyle::LLVM);
assert_eq!(preset.indent_width, 2);
assert_eq!(preset.column_limit, 80);
}
#[test]
fn test_style_preset_google() {
let preset = X86ClangFormatStylePreset::google();
assert_eq!(preset.name, "Google");
assert_eq!(preset.pointer_alignment, X86PointerAlignment::Left);
assert_eq!(preset.include_order, X86IncludeOrderStyle::Google);
}
#[test]
fn test_style_preset_chromium() {
let preset = X86ClangFormatStylePreset::chromium();
assert_eq!(preset.name, "Chromium");
}
#[test]
fn test_style_preset_mozilla() {
let preset = X86ClangFormatStylePreset::mozilla();
assert_eq!(preset.column_limit, 99);
assert_eq!(preset.pointer_alignment, X86PointerAlignment::Left);
}
#[test]
fn test_style_preset_webkit() {
let preset = X86ClangFormatStylePreset::webkit();
assert_eq!(preset.indent_width, 4);
assert_eq!(preset.column_limit, 120);
}
#[test]
fn test_style_preset_microsoft() {
let preset = X86ClangFormatStylePreset::microsoft();
assert_eq!(preset.brace_style, X86BraceStyle::Microsoft);
assert!(preset.use_tabs);
assert!(preset.space_before_parens);
}
#[test]
fn test_style_preset_gnu() {
let preset = X86ClangFormatStylePreset::gnu();
assert_eq!(preset.brace_style, X86BraceStyle::GNU);
assert_eq!(preset.column_limit, 79);
assert!(preset.space_before_parens);
}
#[test]
fn test_style_preset_apply_to() {
let preset = X86ClangFormatStylePreset::webkit();
let mut fmt = X86ClangFormat::default();
preset.apply_to(&mut fmt);
assert_eq!(fmt.indent.width, 4);
assert_eq!(fmt.line_break.column_limit, 120);
assert_eq!(fmt.brace_style, X86BraceStyle::WebKit);
}
#[test]
fn test_style_preset_all_names() {
let names = X86ClangFormatStylePreset::all_preset_names();
assert_eq!(names.len(), 7);
assert!(names.contains(&"LLVM"));
assert!(names.contains(&"GNU"));
}
#[test]
fn test_style_preset_by_name() {
assert!(X86ClangFormatStylePreset::by_name("llvm").is_some());
assert!(X86ClangFormatStylePreset::by_name("GOOGLE").is_some());
assert!(X86ClangFormatStylePreset::by_name("gnu").is_some());
assert!(X86ClangFormatStylePreset::by_name("nonexistent").is_none());
}
#[test]
fn test_style_preset_merge() {
let base = X86ClangFormatStylePreset::llvm();
let other = X86ClangFormatStylePreset::webkit();
let merged = base.merge(&other);
assert_eq!(merged.indent_width, 4); assert_eq!(merged.column_limit, 120); assert_eq!(merged.brace_style, X86BraceStyle::WebKit);
}
#[test]
fn test_token_annotator_new() {
let annotator = X86TokenAnnotator::default();
assert!(!annotator.keywords.is_empty());
}
#[test]
fn test_token_annotate_punctuation() {
let annotator = X86TokenAnnotator::default();
assert_eq!(annotator.annotate_token("{"), X86TokenAnnotation::OpenBrace);
assert_eq!(
annotator.annotate_token("}"),
X86TokenAnnotation::CloseBrace
);
assert_eq!(annotator.annotate_token("("), X86TokenAnnotation::OpenParen);
assert_eq!(
annotator.annotate_token(")"),
X86TokenAnnotation::CloseParen
);
assert_eq!(annotator.annotate_token(";"), X86TokenAnnotation::Semicolon);
assert_eq!(annotator.annotate_token(","), X86TokenAnnotation::Comma);
}
#[test]
fn test_token_annotate_keywords() {
let annotator = X86TokenAnnotator::default();
assert_eq!(annotator.annotate_token("if"), X86TokenAnnotation::Keyword);
assert_eq!(
annotator.annotate_token("return"),
X86TokenAnnotation::Keyword
);
assert_eq!(
annotator.annotate_token("class"),
X86TokenAnnotation::Keyword
);
}
#[test]
fn test_token_annotate_types() {
let annotator = X86TokenAnnotator::default();
assert_eq!(
annotator.annotate_token("int"),
X86TokenAnnotation::TypeName
);
assert_eq!(
annotator.annotate_token("void"),
X86TokenAnnotation::TypeName
);
}
#[test]
fn test_token_annotate_operators() {
let annotator = X86TokenAnnotator::default();
assert_eq!(
annotator.annotate_token("+"),
X86TokenAnnotation::BinaryOperator
);
assert_eq!(
annotator.annotate_token("=="),
X86TokenAnnotation::BinaryOperator
);
assert_eq!(
annotator.annotate_token("="),
X86TokenAnnotation::Assignment
);
assert_eq!(
annotator.annotate_token("+="),
X86TokenAnnotation::Assignment
);
}
#[test]
fn test_token_annotate_identifier() {
let annotator = X86TokenAnnotator::default();
assert_eq!(
annotator.annotate_token("myVar"),
X86TokenAnnotation::Identifier
);
assert_eq!(
annotator.annotate_token("x1"),
X86TokenAnnotation::Identifier
);
}
#[test]
fn test_token_annotate_line_simple() {
let annotator = X86TokenAnnotator::default();
let tokens = annotator.annotate_line("int x = 5;");
assert!(tokens.len() > 0);
let annotations: Vec<X86TokenAnnotation> = tokens.iter().map(|(_, a)| *a).collect();
assert!(annotations.contains(&X86TokenAnnotation::TypeName));
assert!(annotations.contains(&X86TokenAnnotation::Identifier));
assert!(annotations.contains(&X86TokenAnnotation::Assignment));
assert!(annotations.contains(&X86TokenAnnotation::NumericLiteral));
}
#[test]
fn test_token_annotate_line_string() {
let annotator = X86TokenAnnotator::default();
let tokens = annotator.annotate_line("printf(\"hello\");");
let annotations: Vec<X86TokenAnnotation> = tokens.iter().map(|(_, a)| *a).collect();
assert!(annotations.contains(&X86TokenAnnotation::StringLiteral));
}
#[test]
fn test_token_needs_space_before() {
let annotator = X86TokenAnnotator::default();
assert!(annotator.needs_space_before(
X86TokenAnnotation::TypeName,
X86TokenAnnotation::Identifier,
"x"
));
assert!(!annotator.needs_space_before(
X86TokenAnnotation::NumericLiteral,
X86TokenAnnotation::Semicolon,
";"
));
}
#[test]
fn test_token_add_custom_keyword() {
let mut annotator = X86TokenAnnotator::default();
annotator.add_keyword("mykeyword");
assert_eq!(
annotator.annotate_token("mykeyword"),
X86TokenAnnotation::Keyword
);
}
#[test]
fn test_token_add_custom_type() {
let mut annotator = X86TokenAnnotator::default();
annotator.add_type_name("MyType");
assert_eq!(
annotator.annotate_token("MyType"),
X86TokenAnnotation::TypeName
);
}
#[test]
fn test_token_annotation_display() {
assert_eq!(X86TokenAnnotation::LineStart.to_string(), "LineStart");
assert_eq!(X86TokenAnnotation::OpenBrace.to_string(), "OpenBrace");
assert_eq!(X86TokenAnnotation::Unknown.to_string(), "Unknown");
}
#[test]
fn test_break_optimizer_default() {
let opt = X86LineBreakOptimizer::default();
assert_eq!(opt.style.column_limit, 80);
}
#[test]
fn test_break_optimizer_new() {
let style = X86LineBreakStyle { column_limit: 100 };
let opt = X86LineBreakOptimizer::new(style);
assert_eq!(opt.style.column_limit, 100);
}
#[test]
fn test_break_optimizer_no_break_needed() {
let opt = X86LineBreakOptimizer::default();
assert!(opt.find_optimal_break("short line").is_none());
}
#[test]
fn test_break_optimizer_long_line() {
let opt = X86LineBreakOptimizer::default();
let long_line =
"aaaa, bbbb, cccc, dddd, eeee, ffff, gggg, hhhh, iiii, jjjj, kkkk, llll, mmmm, nnnn";
let result = opt.find_optimal_break(long_line);
assert!(result.is_some());
}
#[test]
fn test_break_optimizer_reformat_line() {
let opt = X86LineBreakOptimizer::default();
let line = "this is a very long line that should need breaking because it exceeds eighty characters which is the default limit";
let reformatted = opt.reformat_line(line);
assert!(reformatted.contains('\n') || reformatted.len() <= 80);
}
#[test]
fn test_break_optimizer_short_line_passes_through() {
let opt = X86LineBreakOptimizer::default();
let line = "short";
assert_eq!(opt.reformat_line(line), "short");
}
#[test]
fn test_break_optimizer_calculate_penalty() {
let opt = X86LineBreakOptimizer::default();
let lines = vec!["short", "also short"];
assert_eq!(opt.calculate_block_penalty(&lines), 0);
let long_lines = vec!["a very long line that is over eighty characters long and should incur a penalty for the optimizer"];
let penalty = opt.calculate_block_penalty(&long_lines);
assert!(penalty > 0);
}
#[test]
fn test_break_optimizer_can_single_line_function() {
let mut opt = X86LineBreakOptimizer::default();
opt.style.allow_short_functions_on_a_single_line = true;
assert!(opt.can_single_line(&["int x = 5;", "return x;"]));
}
#[test]
fn test_break_optimizer_cannot_single_line_long() {
let mut opt = X86LineBreakOptimizer::default();
opt.style.allow_short_functions_on_a_single_line = true;
assert!(!opt.can_single_line(&["line1", "line2", "line3", "line4", "line5"]));
}
#[test]
fn test_break_optimizer_can_single_line_if() {
let mut opt = X86LineBreakOptimizer::default();
opt.style.allow_short_if_statements_on_a_single_line = true;
assert!(opt.can_single_line_if("x > 0", "return 1;"));
}
#[test]
fn test_break_candidate_new() {
let c = X86BreakCandidate::new(42, 500, "test break");
assert_eq!(c.column, 42);
assert_eq!(c.penalty, 500);
assert_eq!(c.reason, "test break");
}
#[test]
fn test_rewriter_new() {
let rw = X86SourceRewriter::new(PathBuf::from("test.c"), "content");
assert_eq!(rw.original_content, "content");
assert_eq!(rw.current_content, "content");
assert!(!rw.is_modified);
}
#[test]
fn test_rewriter_apply_insertion() {
let mut rw = X86SourceRewriter::new(PathBuf::from("test.c"), "hello world");
let edit = X86SourceEdit::insertion(5, " beautiful", "insert adjective");
rw.apply_edit(&edit).unwrap();
assert_eq!(rw.current_content, "hello beautiful world");
assert!(rw.is_modified);
}
#[test]
fn test_rewriter_apply_deletion() {
let mut rw = X86SourceRewriter::new(PathBuf::from("test.c"), "hello beautiful world");
let edit = X86SourceEdit::deletion(5, 10, "remove adjective");
rw.apply_edit(&edit).unwrap();
assert_eq!(rw.current_content, "hello world");
}
#[test]
fn test_rewriter_apply_replacement() {
let mut rw = X86SourceRewriter::new(PathBuf::from("test.c"), "hello world");
let edit = X86SourceEdit::replacement(6, 5, "there", "replace world");
rw.apply_edit(&edit).unwrap();
assert_eq!(rw.current_content, "hello there");
}
#[test]
fn test_rewriter_edit_invalid_offset() {
let mut rw = X86SourceRewriter::new(PathBuf::from("test.c"), "short");
let edit = X86SourceEdit::insertion(100, "text", "invalid");
assert!(rw.apply_edit(&edit).is_err());
}
#[test]
fn test_rewriter_get_line() {
let rw = X86SourceRewriter::new(PathBuf::from("test.c"), "line1\nline2\nline3\n");
assert_eq!(rw.get_line(1), Some("line1"));
assert_eq!(rw.get_line(2), Some("line2"));
assert_eq!(rw.get_line(99), None);
}
#[test]
fn test_rewriter_get_lines() {
let rw = X86SourceRewriter::new(PathBuf::from("test.c"), "a\nb\nc\nd\n");
let range = rw.get_lines(2, 3);
assert_eq!(range, "b\nc");
}
#[test]
fn test_rewriter_insert_at_line_start() {
let mut rw = X86SourceRewriter::new(PathBuf::from("test.c"), "int x;\nint y;\n");
rw.insert_at_line_start(2, "// comment\n").unwrap();
assert!(rw.current_content.contains("// comment"));
}
#[test]
fn test_rewriter_replace_line() {
let mut rw = X86SourceRewriter::new(PathBuf::from("test.c"), "int x = 5;\n");
rw.replace_line(1, "float x = 5.0f;").unwrap();
assert!(rw.current_content.contains("float"));
}
#[test]
fn test_rewriter_delete_line() {
let mut rw = X86SourceRewriter::new(PathBuf::from("test.c"), "line1\nline2\n");
rw.delete_line(1).unwrap();
assert_eq!(rw.current_content.trim(), "line2");
}
#[test]
fn test_rewriter_undo() {
let mut rw = X86SourceRewriter::new(PathBuf::from("test.c"), "hello world");
let edit = X86SourceEdit::insertion(5, " beautiful", "test");
rw.apply_edit(&edit).unwrap();
assert_eq!(rw.current_content, "hello beautiful world");
rw.undo().unwrap();
assert_eq!(rw.current_content, "hello world");
}
#[test]
fn test_rewriter_undo_empty() {
let mut rw = X86SourceRewriter::new(PathBuf::from("test.c"), "hello");
assert!(rw.undo().is_err());
}
#[test]
fn test_rewriter_reset() {
let mut rw = X86SourceRewriter::new(PathBuf::from("test.c"), "hello");
let edit = X86SourceEdit::insertion(0, "prefix ", "test");
rw.apply_edit(&edit).unwrap();
assert!(rw.is_modified);
rw.reset();
assert!(!rw.is_modified);
assert_eq!(rw.current_content, "hello");
}
#[test]
fn test_rewriter_unified_diff_no_changes() {
let rw = X86SourceRewriter::new(PathBuf::from("test.c"), "hello\n");
let diff = rw.unified_diff();
assert!(diff.contains("hello"));
assert!(!diff.contains("+"));
}
#[test]
fn test_source_edit_insertion() {
let edit = X86SourceEdit::insertion(0, "text", "desc");
assert_eq!(edit.length, 0);
assert_eq!(edit.replacement, "text");
}
#[test]
fn test_source_edit_deletion() {
let edit = X86SourceEdit::deletion(5, 10, "desc");
assert_eq!(edit.replacement, "");
}
#[test]
fn test_tool_runner_default() {
let runner = X86ToolRunner::default();
assert!(runner.results.is_empty());
assert!(!runner.stop_on_error);
}
#[test]
fn test_tool_runner_new() {
let runner = X86ToolRunner::new();
assert!(runner.results.is_empty());
}
#[test]
fn test_tool_runner_with_config() {
let config = X86ToolingConfig { jobs: 4 };
let runner = X86ToolRunner::with_config(config);
assert_eq!(runner.config.jobs, 4);
}
#[test]
fn test_tool_run_result_success() {
let result = X86ToolRunResult::success("test-tool");
assert!(result.is_success());
assert_eq!(result.tool_name, "test-tool");
assert_eq!(result.exit_code, 0);
}
#[test]
fn test_tool_run_result_failure() {
let result = X86ToolRunResult::failure("test-tool", "something went wrong");
assert!(!result.is_success());
assert_eq!(result.exit_code, 1);
assert_eq!(result.stderr, "something went wrong");
}
#[test]
fn test_tool_run_result_summary() {
let result = X86ToolRunResult {
tool_name: "clang-format".into(),
exit_code: 0,
stdout: String::new(),
stderr: String::new(),
duration_ms: 150,
files_processed: 10,
changes_made: 3,
};
let summary = result.summary();
assert!(summary.contains("OK"));
assert!(summary.contains("10"));
assert!(summary.contains("150"));
}
#[test]
fn test_tool_runner_run_tool_success() {
let mut runner = X86ToolRunner::new();
let result = runner.run_tool("test", || Ok(X86ToolRunResult::success("test")));
assert!(result.is_success());
assert_eq!(runner.results.len(), 1);
}
#[test]
fn test_tool_runner_run_tool_failure() {
let mut runner = X86ToolRunner::new();
let result = runner.run_tool("test", || Err("error occurred".into()));
assert!(!result.is_success());
assert!(result.stderr.contains("error"));
}
#[test]
fn test_tool_runner_print_summary() {
let mut runner = X86ToolRunner::new();
runner.run_tool("fmt", || Ok(X86ToolRunResult::success("fmt")));
runner.run_tool("tidy", || Ok(X86ToolRunResult::success("tidy")));
let summary = runner.print_summary();
assert!(summary.contains("fmt"));
assert!(summary.contains("tidy"));
assert!(summary.contains("2"));
}
#[test]
fn test_tool_runner_clear() {
let mut runner = X86ToolRunner::new();
runner.run_tool("test", || Ok(X86ToolRunResult::success("test")));
assert!(!runner.results.is_empty());
runner.clear();
assert!(runner.results.is_empty());
}
#[test]
fn test_tool_runner_exit_code_all_pass() {
let mut runner = X86ToolRunner::new();
runner.run_tool("a", || Ok(X86ToolRunResult::success("a")));
assert_eq!(runner.exit_code(), 0);
}
#[test]
fn test_tool_runner_exit_code_with_failure() {
let mut runner = X86ToolRunner::new();
runner.run_tool("a", || Ok(X86ToolRunResult::success("a")));
runner.run_tool("b", || Err("failed".into()));
assert_eq!(runner.exit_code(), 1);
}
#[test]
fn test_diag_consumer_default() {
let consumer = X86DiagnosticConsumer::default();
assert!(consumer.diagnostics.is_empty());
assert!(consumer.min_severity.is_some());
}
#[test]
fn test_diag_consumer_consume() {
let mut consumer = X86DiagnosticConsumer::new();
let diag = X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"test",
"test-check",
PathBuf::from("f.c"),
1,
1,
);
assert!(consumer.consume(diag));
assert_eq!(consumer.diagnostics.len(), 1);
}
#[test]
fn test_diag_consumer_consume_filtered_severity() {
let mut consumer = X86DiagnosticConsumer::new();
consumer.min_severity = Some(X86TidySeverity::Error);
let diag = X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"test",
"check",
PathBuf::from("f.c"),
1,
1,
);
assert!(!consumer.consume(diag));
assert!(consumer.diagnostics.is_empty());
}
#[test]
fn test_diag_consumer_consume_all() {
let mut consumer = X86DiagnosticConsumer::new();
let diags = vec![
X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"w1",
"c1",
PathBuf::from("f.c"),
1,
1,
),
X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"w2",
"c2",
PathBuf::from("f.c"),
2,
1,
),
];
let count = consumer.consume_all(diags);
assert_eq!(count, 2);
}
#[test]
fn test_diag_consumer_by_severity() {
let mut consumer = X86DiagnosticConsumer::new();
consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Error,
"err",
"chk",
PathBuf::from("f.c"),
1,
1,
));
consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"warn",
"chk",
PathBuf::from("f.c"),
2,
1,
));
let by_sev = consumer.by_severity();
assert!(by_sev.contains_key(&X86TidySeverity::Error));
assert!(by_sev.contains_key(&X86TidySeverity::Warning));
}
#[test]
fn test_diag_consumer_by_check() {
let mut consumer = X86DiagnosticConsumer::new();
consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"msg",
"check-a",
PathBuf::from("f.c"),
1,
1,
));
consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"msg",
"check-b",
PathBuf::from("f.c"),
2,
1,
));
let by_check = consumer.by_check();
assert_eq!(by_check.len(), 2);
}
#[test]
fn test_diag_consumer_render() {
let mut consumer = X86DiagnosticConsumer::new();
consumer.consume(
X86TidyDiagnostic::new(
X86TidySeverity::Error,
"test error",
"my-check",
PathBuf::from("src/main.c"),
10,
5,
)
.with_fixit("correction"),
);
let rendered = consumer.render();
assert!(rendered.contains("test error"));
assert!(rendered.contains("correction"));
}
#[test]
fn test_diag_consumer_severity_counts() {
let mut consumer = X86DiagnosticConsumer::new();
consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Fatal,
"f",
"c",
PathBuf::from("f.c"),
1,
1,
));
let counts = consumer.severity_counts();
assert!(!counts.is_empty());
}
#[test]
fn test_diag_consumer_warning_count() {
let mut consumer = X86DiagnosticConsumer::new();
consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"w",
"c",
PathBuf::from("f.c"),
1,
1,
));
assert_eq!(consumer.warning_count(), 1);
assert_eq!(consumer.error_count(), 0);
}
#[test]
fn test_diag_consumer_has_errors() {
let mut consumer = X86DiagnosticConsumer::new();
consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Error,
"e",
"c",
PathBuf::from("f.c"),
1,
1,
));
assert!(consumer.has_errors());
}
#[test]
fn test_diag_consumer_clear() {
let mut consumer = X86DiagnosticConsumer::new();
consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"w",
"c",
PathBuf::from("f.c"),
1,
1,
));
consumer.clear();
assert!(consumer.diagnostics.is_empty());
}
#[test]
fn test_diag_consumer_unique_checks() {
let mut consumer = X86DiagnosticConsumer::new();
consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"a",
"check1",
PathBuf::from("f.c"),
1,
1,
));
consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"b",
"check2",
PathBuf::from("f.c"),
2,
1,
));
consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"c",
"check1",
PathBuf::from("f.c"),
3,
1,
));
let unique = consumer.unique_check_names();
assert_eq!(unique.len(), 2);
}
#[test]
fn test_diag_consumer_max_diagnostics() {
let mut consumer = X86DiagnosticConsumer::new();
consumer.max_diagnostics = 2;
assert!(consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"1",
"c",
PathBuf::from("f.c"),
1,
1,
)));
assert!(consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"2",
"c",
PathBuf::from("f.c"),
2,
1,
)));
assert!(!consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"3",
"c",
PathBuf::from("f.c"),
3,
1,
)));
assert_eq!(consumer.suppressed_count, 1);
}
#[test]
fn test_diag_consumer_check_filter() {
let mut consumer = X86DiagnosticConsumer::new();
consumer.check_filter = Some(vec!["bugprone".into()]);
assert!(consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"msg",
"bugprone-assert",
PathBuf::from("f.c"),
1,
1,
)));
assert!(!consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"msg",
"modernize-nullptr",
PathBuf::from("f.c"),
1,
1,
)));
}
#[test]
fn test_pipeline_default() {
let pipeline = X86ToolingPipeline::default();
assert!(pipeline.consumer.diagnostics.is_empty());
}
#[test]
fn test_pipeline_new() {
let pipeline = X86ToolingPipeline::new();
assert!(pipeline.consumer.diagnostics.is_empty());
}
#[test]
fn test_pipeline_report() {
let pipeline = X86ToolingPipeline::new();
let report = pipeline.report();
assert!(report.contains("Pipeline Report"));
assert!(report.contains("Files processed"));
}
#[test]
fn test_pipeline_reset() {
let mut pipeline = X86ToolingPipeline::new();
pipeline.tools.stats.files_processed = 10;
pipeline.consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"msg",
"c",
PathBuf::from("f.c"),
1,
1,
));
pipeline.reset();
assert_eq!(pipeline.tools.stats.files_processed, 0);
assert!(pipeline.consumer.diagnostics.is_empty());
}
#[test]
fn test_integration_format_preserves_strings() {
let fmt = X86ClangFormat::default();
let input = r#"printf(" hello world ");"#;
let result = fmt.format_source(input, Path::new("test.c")).unwrap();
assert!(result.contains("hello world"));
}
#[test]
fn test_integration_tidy_all_categories_registered() {
let tidy = X86ClangTidy::default();
let categories: HashSet<X86TidyCategory> = tidy.checks.iter().map(|c| c.category).collect();
assert!(categories.contains(&X86TidyCategory::Bugprone));
assert!(categories.contains(&X86TidyCategory::Modernize));
assert!(categories.contains(&X86TidyCategory::Performance));
assert!(categories.contains(&X86TidyCategory::Readability));
assert!(categories.len() >= 4);
}
#[test]
fn test_integration_include_fixer_handles_empty_source() {
let fixer = X86IncludeFixer::default();
let result = fixer.fix_source("");
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result, "\n");
}
#[test]
fn test_integration_include_fixer_handles_no_includes() {
let fixer = X86IncludeFixer::default();
let source = "int main() { return 0; }\n";
let result = fixer.fix_source(source);
assert!(result.is_ok());
}
#[test]
fn test_integration_refactor_multiple_operations() {
let mut tool = X86RefactoringTool::default();
tool.rename("a", "b", None::<&str>);
tool.rename("c", "d", None::<&str>);
assert_eq!(tool.operations.len(), 2);
}
#[test]
fn test_integration_query_matches_multiple_matchers() {
let query = X86ClangQuery::new();
let matchers = vec![
X86QueryMatcher::node_kind("function"),
X86QueryMatcher::node_kind("variable"),
];
assert_eq!(matchers.len(), 2);
for m in &matchers {
assert!(!m.to_query_string().is_empty());
}
}
#[test]
fn test_integration_format_classify_all_categories() {
assert_eq!(
X86ClangFormat::classify_include("#include <stdio.h>"),
X86IncludeCategory::CSystemHeader
);
assert_eq!(
X86ClangFormat::classify_include("#include <vector>"),
X86IncludeCategory::CppSystemHeader
);
assert_eq!(
X86ClangFormat::classify_include("#include \"local.h\""),
X86IncludeCategory::LocalHeader
);
assert_eq!(
X86ClangFormat::classify_include("#include <sys/types.h>"),
X86IncludeCategory::SystemHeader
);
}
#[test]
fn test_integration_tidy_suppress_patterns() {
let mut tidy = X86ClangTidy::default();
tidy.suppress_patterns.push("NOLINT".to_string());
assert!(tidy.is_suppressed("// NOLINT: this is fine"));
assert!(!tidy.is_suppressed("int x = 5;"));
}
#[test]
fn test_integration_annotator_complex_line() {
let annotator = X86TokenAnnotator::default();
let tokens = annotator.annotate_line("if (x > 0) { return x + 1; }");
let annotations: Vec<X86TokenAnnotation> = tokens.iter().map(|(_, a)| *a).collect();
assert!(annotations.contains(&X86TokenAnnotation::Keyword)); assert!(annotations.contains(&X86TokenAnnotation::BinaryOperator)); assert!(annotations.contains(&X86TokenAnnotation::OpenBrace));
assert!(annotations.contains(&X86TokenAnnotation::CloseBrace));
}
#[test]
fn test_integration_break_optimizer_with_long_expression() {
let mut opt = X86LineBreakOptimizer::default();
opt.style.column_limit = 40;
let line = "int result = compute_value(a, b, c, d, e, f);";
let reformatted = opt.reformat_line(line);
assert!(reformatted.contains('\n') || reformatted.len() <= 40);
}
#[test]
fn test_integration_rewriter_multiple_edits() {
let mut rw = X86SourceRewriter::new(PathBuf::from("test.c"), "hello world");
let edit1 = X86SourceEdit::insertion(0, "say ", "add say");
let edit2 = X86SourceEdit::insertion(15, "!", "add exclamation");
rw.apply_edit(&edit2).unwrap();
rw.apply_edit(&edit1).unwrap();
assert_eq!(rw.current_content, "say hello world!");
}
#[test]
fn test_integration_pipeline_all_components() {
let pipeline = X86ToolingPipeline::new();
assert!(pipeline.tools.format.enabled);
assert!(pipeline.tools.tidy.enabled);
assert!(pipeline.tools.include_fixer.enabled);
assert!(pipeline.consumer.diagnostics.is_empty());
assert!(pipeline.runner.results.is_empty());
}
#[test]
fn test_integration_diag_consumer_pipeline_flow() {
let mut pipeline = X86ToolingPipeline::new();
let diags = vec![X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"test",
"check",
PathBuf::from("f.c"),
1,
1,
)];
pipeline.consumer.consume_all(diags);
assert!(!pipeline.consumer.diagnostics.is_empty());
let report = pipeline.report();
assert!(report.contains("WARNINGS: 1"));
}
#[test]
fn test_integration_style_presets_full_roundtrip() {
for name in X86ClangFormatStylePreset::all_preset_names() {
let preset = X86ClangFormatStylePreset::by_name(name).unwrap();
let mut fmt = X86ClangFormat::default();
preset.apply_to(&mut fmt);
assert_eq!(fmt.brace_style, preset.brace_style);
assert_eq!(fmt.indent.width, preset.indent_width);
assert_eq!(fmt.line_break.column_limit, preset.column_limit);
}
}
#[test]
fn test_integration_refactor_replacement_large() {
let repl = X86RefactorReplacement::new(
PathBuf::from("test.c"),
1,
1,
1,
5,
"longer_replacement_text",
"test large replacement",
);
assert_eq!(repl.new_text.len(), 24);
}
#[test]
fn test_integration_query_dump_with_many_lines() {
let query = X86ClangQuery::new();
let source = "int a;\nint b;\nint c;\nfloat d;\ndouble e;\n";
let dump = query.dump_ast_string(source);
assert!(dump.contains("VarDecl"));
assert!(dump.lines().count() > 3);
}
#[test]
fn test_integration_tidy_all_checks_unique() {
let tidy = X86ClangTidy::default();
let mut names: Vec<&str> = tidy.checks.iter().map(|c| c.name.as_str()).collect();
names.sort();
let orig_len = names.len();
names.dedup();
assert_eq!(orig_len, names.len(), "All check names should be unique");
}
#[test]
fn test_integration_tidy_checks_over_50() {
let tidy = X86ClangTidy::default();
assert!(
tidy.checks.len() >= 50,
"Should have at least 50 checks, has {}",
tidy.checks.len()
);
}
#[test]
fn test_integration_format_block_empty() {
let fmt = X86ClangFormat::default();
let result = fmt.format_block("").unwrap();
assert!(result.is_empty() || result == "\n");
}
#[test]
fn test_integration_format_block_multi_line() {
let fmt = X86ClangFormat::default();
let input = "int a = 1;\nint b = 2;\n";
let result = fmt.format_block(input).unwrap();
assert!(result.contains("int a = 1;"));
assert!(result.contains("int b = 2;"));
}
#[test]
fn test_integration_query_matcher_complex_nested() {
let matcher = X86QueryMatcher::all_of(vec![
X86QueryMatcher::node_kind("function"),
X86QueryMatcher::has_descendant(X86QueryMatcher::any_of(vec![
X86QueryMatcher::node_kind("call"),
X86QueryMatcher::node_kind("return"),
])),
X86QueryMatcher::unless(X86QueryMatcher::has_name("ignore_me")),
]);
let s = matcher.to_query_string();
assert!(s.contains("allOf"));
assert!(s.contains("anyOf"));
assert!(s.contains("unless"));
}
#[test]
fn test_integration_break_optimizer_edge_cases() {
let opt = X86LineBreakOptimizer::default();
assert!(opt.find_optimal_break("").is_none());
let line = "a".repeat(80);
assert!(opt.find_optimal_break(&line).is_none());
let line = "a".repeat(81);
let result = opt.find_optimal_break(&line);
assert!(result.is_some());
}
#[test]
fn test_integration_rewriter_edit_boundary() {
let mut rw = X86SourceRewriter::new(PathBuf::from("test.c"), "abc");
let edit = X86SourceEdit::insertion(3, "d", "append");
rw.apply_edit(&edit).unwrap();
assert_eq!(rw.current_content, "abcd");
}
#[test]
fn test_integration_rewriter_edit_exact_deletion() {
let mut rw = X86SourceRewriter::new(PathBuf::from("test.c"), "hello");
let edit = X86SourceEdit::deletion(0, 5, "delete all");
rw.apply_edit(&edit).unwrap();
assert_eq!(rw.current_content, "");
}
#[test]
fn test_integration_diag_consumer_mixed_severities() {
let mut consumer = X86DiagnosticConsumer::new();
consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Fatal,
"f",
"c",
PathBuf::from("f.c"),
1,
1,
));
consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Note,
"n",
"c",
PathBuf::from("f.c"),
1,
1,
));
assert_eq!(consumer.diagnostics.len(), 1);
}
#[test]
fn test_integration_tooling_result_default() {
let result = X86ToolingResult::default();
assert!(result.format_results.is_empty());
assert!(result.is_success());
}
#[test]
fn test_integration_format_column_limit_with_long_line() {
let mut fmt = X86ClangFormat::default();
fmt.line_break.column_limit = 20;
let input = "int veryLongVariableName = someFunctionCall();\n";
let result = fmt.format_source(input, Path::new("test.c")).unwrap();
assert!(!result.is_empty());
}
#[test]
fn test_integration_tidy_non_existent_check() {
let mut tidy = X86ClangTidy::default();
tidy.enable_check("nonexistent-check");
tidy.disable_check("nonexistent-check");
assert!(!tidy.is_check_enabled("nonexistent-check"));
}
#[test]
fn test_integration_include_fixer_find_referenced_empty() {
let fixer = X86IncludeFixer::default();
let symbols = fixer.find_referenced_symbols("");
assert!(symbols.is_empty());
}
#[test]
fn test_integration_include_fixer_detect_unused_empty() {
let fixer = X86IncludeFixer::default();
let unused = fixer.detect_unused_includes("");
assert!(unused.is_empty());
}
#[test]
fn test_include_fixer_iwyu_map_default() {
let fixer = X86IncludeFixer::default();
assert!(fixer.iwyu_map.contains_key("stdio.h"));
assert!(fixer.iwyu_map.contains_key("string"));
}
#[test]
fn test_include_fixer_symbol_map_comprehensive() {
let fixer = X86IncludeFixer::default();
let symbols = [
"printf",
"malloc",
"free",
"memcpy",
"strlen",
"std::vector",
"std::string",
];
for sym in &symbols {
assert!(
fixer.is_standard_symbol(sym),
"Symbol {} should be known",
sym
);
}
}
#[test]
fn test_include_fixer_fix_source_preserves_non_includes() {
let fixer = X86IncludeFixer::default();
let source = "#include <stdio.h>\nint main() { return 0; }\n";
let result = fixer.fix_source(source).unwrap();
assert!(result.contains("int main()"));
assert!(result.contains("#include <stdio.h>"));
}
#[test]
fn test_include_fixer_fix_source_adds_missing() {
let fixer = X86IncludeFixer::default();
let source = "int main() { printf(\"hello\"); return 0; }\n";
let result = fixer.fix_source(source).unwrap();
assert!(result.contains("#include") || result.contains("printf"));
}
#[test]
fn test_format_line_with_braces_attached() {
let fmt = X86ClangFormat::with_style(X86BraceStyle::LLVM);
let result = fmt.format_line("if (x){");
assert!(result.contains("if (x) {"));
}
#[test]
fn test_format_line_with_trailing_spaces() {
let fmt = X86ClangFormat::default();
let result = fmt.format_line("int x = 5; ");
assert!(!result.ends_with(' '));
}
#[test]
fn test_format_source_with_comments() {
let fmt = X86ClangFormat::default();
let input = "// this is a comment\nint x = 5;\n/* block */\n";
let result = fmt.format_source(input, Path::new("test.c")).unwrap();
assert!(result.contains("// this is a comment"));
assert!(result.contains("int x = 5;"));
assert!(result.contains("/* block */"));
}
#[test]
fn test_format_source_trailing_newline() {
let fmt = X86ClangFormat::default();
let input = "int x = 5;";
let result = fmt.format_source(input, Path::new("test.c")).unwrap();
assert!(result.ends_with('\n'));
}
#[test]
fn test_format_comment_doxygen() {
let fmt = X86ClangFormat::default();
let result = fmt.format_comment("/// Brief description");
assert_eq!(result, "/// Brief description");
}
#[test]
fn test_format_comment_block_trailing_star() {
let fmt = X86ClangFormat::default();
let result = fmt.format_comment("/* single line */");
assert!(result.contains("/*"));
assert!(result.contains("*/"));
}
#[test]
fn test_format_macro_empty_body() {
let fmt = X86ClangFormat::default();
let result = fmt.format_macro("NOP", "");
assert_eq!(result, "#define NOP");
}
#[test]
fn test_format_sort_includes_custom_priority() {
let mut fmt = X86ClangFormat::default();
fmt.include_order = X86IncludeOrderStyle::Custom;
fmt.custom_include_order = vec!["proj".into(), "third".into()];
let includes = vec![
(
"#include <third/lib.h>".into(),
X86IncludeCategory::ThirdPartyHeader,
),
(
"#include <proj/core.h>".into(),
X86IncludeCategory::ProjectHeader,
),
];
let sorted = fmt.sort_includes(&includes);
assert_eq!(sorted.len(), 2);
}
#[test]
fn test_format_sort_includes_none_preserves_order() {
let mut fmt = X86ClangFormat::default();
fmt.include_order = X86IncludeOrderStyle::None;
let includes = vec![
("#include <z.h>".into(), X86IncludeCategory::UnknownHeader),
("#include <a.h>".into(), X86IncludeCategory::UnknownHeader),
];
let sorted = fmt.sort_includes(&includes);
assert_eq!(sorted[0], "#include <z.h>");
assert_eq!(sorted[1], "#include <a.h>");
}
#[test]
fn test_tidy_check_assert_side_effect_with_increment() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_assert_side_effect("assert(x++);", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_assert_side_effect_with_assign() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_assert_side_effect("assert(x = 5);", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_assert_side_effect_clean() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_assert_side_effect("assert(x > 0);", 1, Path::new("test.c"));
assert!(diags.is_empty());
}
#[test]
fn test_tidy_check_suspicious_semicolon_for() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_suspicious_semicolon(
"for (int i = 0; i < 10; i++);",
1,
Path::new("test.c"),
);
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_unused_return_scanf() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_unused_return_value("scanf(\"%d\", &x);", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_unused_return_malloc() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_unused_return_value("malloc(100);", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_use_auto_unique_ptr() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_use_auto(
"std::unique_ptr<int> p = std::make_unique<int>(5);",
1,
Path::new("test.c"),
);
assert!(!diags.is_empty());
assert!(diags[0].fixit.as_deref() == Some("auto"));
}
#[test]
fn test_tidy_check_use_auto_shared_ptr() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_use_auto(
"std::shared_ptr<int> p = std::make_shared<int>(5);",
1,
Path::new("test.c"),
);
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_use_noexcept_swap() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_use_noexcept("void swap(T& a, T& b);", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_noexcept_move_constructor() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_noexcept_move("MyClass(MyClass&& other);", 1, Path::new("test.c"));
assert!(!diags.is_empty());
assert!(diags[0].fixit.as_deref() == Some("noexcept"));
}
#[test]
fn test_tidy_check_raw_string_literal_escaped_quotes() {
let tidy = X86ClangTidy::default();
let diags =
tidy.check_raw_string_literal("\"hello \\\"world\\\"\"", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_pass_by_value_const_ref_move() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_pass_by_value(
"void foo(const std::string& s) { bar(std::move(s)); }",
1,
Path::new("test.c"),
);
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_implicit_bool_named_var() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_implicit_bool("if (myFlag)", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_implicit_bool_true_false_ok() {
let tidy = X86ClangTidy::default();
let diags1 = tidy.check_implicit_bool("if (true)", 1, Path::new("test.c"));
let diags2 = tidy.check_implicit_bool("if (false)", 1, Path::new("test.c"));
assert!(diags1.is_empty());
assert!(diags2.is_empty());
}
#[test]
fn test_tidy_check_literal_suffix_lowercase_l() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_literal_suffix("long x = 123l;", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_literal_suffix_uppercase_l_ok() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_literal_suffix("long x = 123L;", 1, Path::new("test.c"));
assert!(diags.is_empty());
}
#[test]
fn test_tidy_check_too_small_loop_var_size_t() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_too_small_loop_var(
"for (int i = 0; i < vec.size(); i++)",
1,
Path::new("test.c"),
);
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_sizeof_expression_with_addition() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_sizeof_expression("int x = sizeof a + b;", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_vector_operation_push_back() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_vector_operation(
"std::vector<int> v; v.push_back(1);",
1,
Path::new("test.c"),
);
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_string_concat_append() {
let tidy = X86ClangTidy::default();
let diags =
tidy.check_string_concat("std::string s; s += \"hello\";", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_unnecessary_value_param_string() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_unnecessary_value_param(
"void f(std::string s, int x);",
1,
Path::new("test.c"),
);
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_unnecessary_copy_auto_with_get() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_unnecessary_copy("auto val = map.get(key);", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_multiple_statement_macro_define() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_multiple_statement_macro(
"#define FOO do_something(); do_other();",
1,
Path::new("test.c"),
);
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_isolate_declaration_pointer_confusion() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_isolate_decl("int* a, b;", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_swapped_memset_args() {
let tidy = X86ClangTidy::default();
let diags =
tidy.check_swapped_arguments("memset(buf, sizeof(buf), 0);", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_query_matcher_has_integer_value_zero() {
let matcher = X86QueryMatcher::HasIntegerValue(0);
assert_eq!(matcher.to_query_string(), "hasIntegerValue(0)");
}
#[test]
fn test_query_matcher_has_string_value() {
let matcher = X86QueryMatcher::HasStringValue("test".into());
assert_eq!(matcher.to_query_string(), "hasStringValue(\"test\")");
}
#[test]
fn test_query_matcher_returns() {
let matcher = X86QueryMatcher::Returns("int".into());
assert_eq!(matcher.to_query_string(), "returns(int)");
}
#[test]
fn test_query_matcher_parameter_count_is() {
let matcher = X86QueryMatcher::ParameterCountIs(3);
assert_eq!(matcher.to_query_string(), "parameterCountIs(3)");
}
#[test]
fn test_query_matcher_any_of_empty() {
let matcher = X86QueryMatcher::AnyOf(vec![]);
assert_eq!(matcher.to_query_string(), "anyOf()");
}
#[test]
fn test_query_matcher_all_of_single() {
let matcher = X86QueryMatcher::AllOf(vec![X86QueryMatcher::Anything]);
assert_eq!(matcher.to_query_string(), "allOf(anything())");
}
#[test]
fn test_query_match_line_returns() {
let query = X86ClangQuery::new();
let matcher = X86QueryMatcher::Returns("void".into());
let matches = query.match_line("void foo() {", 1, Path::new("test.c"), &matcher);
assert!(!matches.is_empty());
}
#[test]
fn test_query_match_line_parameter_count() {
let query = X86ClangQuery::new();
let matcher = X86QueryMatcher::ParameterCountIs(2);
let matches = query.match_line("int add(int a, int b) {", 1, Path::new("test.c"), &matcher);
assert!(!matches.is_empty());
}
#[test]
fn test_query_match_line_parameter_count_mismatch() {
let query = X86ClangQuery::new();
let matcher = X86QueryMatcher::ParameterCountIs(5);
let matches = query.match_line("int add(int a, int b) {", 1, Path::new("test.c"), &matcher);
assert!(matches.is_empty());
}
#[test]
fn test_query_match_line_has_type() {
let query = X86ClangQuery::new();
let matcher = X86QueryMatcher::HasType("int".into());
let matches = query.match_line("int x = 5;", 1, Path::new("test.c"), &matcher);
assert!(!matches.is_empty());
}
#[test]
fn test_query_match_line_has_descendant() {
let query = X86ClangQuery::new();
let matcher =
X86QueryMatcher::HasDescendant(Box::new(X86QueryMatcher::node_kind("return")));
let matches = query.match_line("int foo() { return 5; }", 1, Path::new("test.c"), &matcher);
assert!(matches.is_empty() || !matches.is_empty());
}
#[test]
fn test_query_match_line_unless() {
let query = X86ClangQuery::new();
let matcher = X86QueryMatcher::Unless(Box::new(X86QueryMatcher::has_name("main")));
let matches = query.match_line("int helper() {", 1, Path::new("test.c"), &matcher);
assert!(!matches.is_empty());
}
#[test]
fn test_query_match_line_unless_main_excluded() {
let query = X86ClangQuery::new();
let matcher = X86QueryMatcher::Unless(Box::new(X86QueryMatcher::has_name("main")));
let matches = query.match_line("int main() {", 1, Path::new("test.c"), &matcher);
assert!(matches.is_empty());
}
#[test]
fn test_query_line_matches_class() {
let query = X86ClangQuery::new();
assert!(query.line_matches_kind("class MyClass {", "class"));
assert!(query.line_matches_kind("struct MyStruct {", "class"));
}
#[test]
fn test_query_line_matches_enum() {
let query = X86ClangQuery::new();
assert!(query.line_matches_kind("enum Color {", "enum"));
}
#[test]
fn test_query_line_matches_include() {
let query = X86ClangQuery::new();
assert!(query.line_matches_kind("#include <stdio.h>", "include"));
}
#[test]
fn test_query_line_matches_unknown_kind() {
let query = X86ClangQuery::new();
assert!(!query.line_matches_kind("int x = 5;", "blah"));
}
#[test]
fn test_query_range_empty_range() {
let query = X86ClangQuery::new();
let results = query.query_range("test", Path::new("test.c"), 10, 1, 20, 1);
assert!(results.is_empty());
}
#[test]
fn test_query_types_no_types() {
let query = X86ClangQuery::new();
let results = query.query_types("x = y + z;", Path::new("test.c"));
assert!(results.is_empty());
}
#[test]
fn test_style_preset_by_name_case_insensitive() {
assert!(X86ClangFormatStylePreset::by_name("LLVM").is_some());
assert!(X86ClangFormatStylePreset::by_name("llvm").is_some());
assert!(X86ClangFormatStylePreset::by_name("LlVm").is_some());
}
#[test]
fn test_style_preset_merge_keeps_name() {
let base = X86ClangFormatStylePreset::llvm();
let other = X86ClangFormatStylePreset::google();
let merged = base.merge(&other);
assert!(merged.name.contains("LLVM"));
assert!(merged.name.contains("Google"));
}
#[test]
fn test_token_annotate_two_char_operators() {
let annotator = X86TokenAnnotator::default();
assert_eq!(
annotator.annotate_token("++"),
X86TokenAnnotation::UnaryOperator
);
assert_eq!(
annotator.annotate_token("<="),
X86TokenAnnotation::BinaryOperator
);
assert_eq!(
annotator.annotate_token("+="),
X86TokenAnnotation::Assignment
);
}
#[test]
fn test_token_annotate_empty_token() {
let annotator = X86TokenAnnotator::default();
assert_eq!(annotator.annotate_token(""), X86TokenAnnotation::Whitespace);
}
#[test]
fn test_token_annotate_preprocessor() {
let annotator = X86TokenAnnotator::default();
assert_eq!(
annotator.annotate_token("#"),
X86TokenAnnotation::Preprocessor
);
}
#[test]
fn test_token_annotate_line_comment() {
let annotator = X86TokenAnnotator::default();
assert_eq!(
annotator.annotate_token("// comment"),
X86TokenAnnotation::LineComment
);
}
#[test]
fn test_token_annotate_block_comment() {
let annotator = X86TokenAnnotator::default();
assert_eq!(
annotator.annotate_token("/* comment */"),
X86TokenAnnotation::BlockComment
);
}
#[test]
fn test_token_needs_space_before_close_paren() {
let annotator = X86TokenAnnotator::default();
assert!(!annotator.needs_space_before(
X86TokenAnnotation::Identifier,
X86TokenAnnotation::CloseParen,
")"
));
}
#[test]
fn test_token_needs_space_before_comma() {
let annotator = X86TokenAnnotator::default();
assert!(!annotator.needs_space_before(
X86TokenAnnotation::Identifier,
X86TokenAnnotation::Comma,
","
));
}
#[test]
fn test_token_needs_space_between_identifiers() {
let annotator = X86TokenAnnotator::default();
assert!(annotator.needs_space_before(
X86TokenAnnotation::Identifier,
X86TokenAnnotation::Identifier,
"foo"
));
}
#[test]
fn test_break_optimizer_column_limit_zero() {
let mut style = X86LineBreakStyle::default();
style.column_limit = 0;
let opt = X86LineBreakOptimizer::new(style);
let long_line = "x".repeat(200);
assert_eq!(opt.reformat_line(&long_line), long_line);
}
#[test]
fn test_break_optimizer_single_character_line() {
let opt = X86LineBreakOptimizer::default();
assert_eq!(opt.reformat_line("x"), "x");
}
#[test]
fn test_break_candidate_ordering() {
let a = X86BreakCandidate::new(10, 100, "high penalty");
let b = X86BreakCandidate::new(20, 50, "low penalty");
let c = X86BreakCandidate::new(30, 75, "medium penalty");
let mut candidates = vec![a, b, c];
candidates.sort_by_key(|cand| cand.penalty);
assert_eq!(candidates[0].penalty, 50);
assert_eq!(candidates[1].penalty, 75);
assert_eq!(candidates[2].penalty, 100);
}
#[test]
fn test_refactor_replacement_apply_empty_source() {
let repl = X86RefactorReplacement::new(PathBuf::from("test.c"), 1, 1, 1, 1, "new", "test");
let result = repl.apply("x");
assert!(result.is_ok());
}
#[test]
fn test_refactor_extract_function_single_line() {
let tool = X86RefactoringTool::default();
let source = "int x = 5;\n";
let result = tool.build_extract_function(source, "getX", 1, 1, &[], "int");
assert!(result.is_ok());
}
#[test]
fn test_refactor_extract_function_invalid_range() {
let tool = X86RefactoringTool::default();
let result = tool.build_extract_function("line1\nline2\n", "bad", 5, 10, &[], "void");
assert!(result.is_err());
}
#[test]
fn test_refactor_build_inline() {
let tool = X86RefactoringTool::default();
let result = tool.build_inline("", "func", InlineKind::Function);
assert!(result.is_ok());
}
#[test]
fn test_refactor_build_change_signature() {
let tool = X86RefactoringTool::default();
let result = tool.build_change_signature(
"",
"func",
&[(String::from("int"), String::from("x"))],
&Some("void".into()),
);
assert!(result.is_ok());
}
#[test]
fn test_tool_runner_multiple_tools_timing() {
let mut runner = X86ToolRunner::new();
let r1 = runner.run_tool("tool1", || Ok(X86ToolRunResult::success("tool1")));
let r2 = runner.run_tool("tool2", || Ok(X86ToolRunResult::success("tool2")));
assert_eq!(runner.results.len(), 2);
assert!(r1.duration_ms <= r2.duration_ms || r1.duration_ms >= r2.duration_ms);
}
#[test]
fn test_pipeline_run_format_and_tidy_empty() {
let mut pipeline = X86ToolingPipeline::new();
let (fmt_results, tidy_results) = pipeline.run_format_and_tidy();
assert!(fmt_results.is_empty());
assert!(tidy_results.is_empty());
}
#[test]
fn test_pipeline_continue_on_error() {
let pipeline = X86ToolingPipeline::new();
assert!(pipeline.continue_on_error);
}
#[test]
fn test_tools_run_format_only() {
let mut tools = X86ClangTools::new();
let results = tools.run_format();
assert!(results.is_empty());
assert!(!tools.format.enabled || results.is_empty());
}
#[test]
fn test_tools_run_tidy_only() {
let mut tools = X86ClangTools::new();
let results = tools.run_tidy();
assert!(results.is_empty());
}
#[test]
fn test_integration_end_to_end_minimal() {
let mut tools = X86ClangTools::new();
tools.format.enabled = true;
tools.tidy.enabled = true;
tools.include_fixer.enabled = false;
tools.refactor.operations.clear();
tools.query.matchers.clear();
assert_eq!(tools.format.brace_style, X86BraceStyle::LLVM);
assert!(tools.tidy.enabled_check_count() > 0);
assert!(tools.stats.files_processed == 0);
assert!(tools.stats.errors == 0);
}
#[test]
fn test_integration_all_brace_styles_valid() {
let styles = [
X86BraceStyle::LLVM,
X86BraceStyle::Google,
X86BraceStyle::Chromium,
X86BraceStyle::Mozilla,
X86BraceStyle::WebKit,
X86BraceStyle::Microsoft,
X86BraceStyle::GNU,
];
for style in &styles {
let fmt = X86ClangFormat::with_style(*style);
assert_eq!(fmt.brace_style, *style);
}
}
#[test]
fn test_integration_all_include_orders_valid() {
let orders = [
X86IncludeOrderStyle::LLVM,
X86IncludeOrderStyle::Google,
X86IncludeOrderStyle::Alphabetical,
X86IncludeOrderStyle::Custom,
X86IncludeOrderStyle::None,
];
for order in &orders {
let s = order.to_string();
assert!(!s.is_empty());
}
}
#[test]
fn test_integration_all_pointer_alignments_valid() {
let alignments = [
X86PointerAlignment::Left,
X86PointerAlignment::Right,
X86PointerAlignment::Middle,
];
for align in &alignments {
let s = align.to_string();
assert!(!s.is_empty());
}
}
#[test]
fn test_integration_diag_consumer_full_cycle() {
let mut consumer = X86DiagnosticConsumer::new();
consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Error,
"err",
"chk",
PathBuf::from("f.c"),
1,
1,
));
consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Warning,
"warn",
"chk",
PathBuf::from("f.c"),
2,
1,
));
assert!(consumer.has_errors());
assert_eq!(consumer.warning_count(), 1);
assert_eq!(consumer.error_count(), 1);
let rendered = consumer.render();
assert!(!rendered.is_empty());
consumer.clear();
assert!(consumer.diagnostics.is_empty());
assert!(!consumer.has_errors());
}
#[test]
fn test_integration_rewriter_full_cycle() {
let mut rw =
X86SourceRewriter::new(PathBuf::from("test.c"), "int main() {\n return 0;\n}\n");
assert!(!rw.is_modified);
rw.insert_at_line_start(1, "// Entry point\n").unwrap();
assert!(rw.is_modified);
assert!(rw.current_content.contains("// Entry point"));
rw.undo().unwrap();
assert!(!rw.is_modified);
assert!(!rw.current_content.contains("// Entry point"));
}
#[test]
fn test_integration_query_full_cycle() {
let mut query = X86ClangQuery::new();
query.add_matcher(X86QueryMatcher::node_kind("function"));
query.add_matcher(X86QueryMatcher::has_name("main"));
assert_eq!(query.matchers.len(), 2);
let source = "int main() {\n return 0;\n}\n";
let results = query.query_file(Path::new("test.c")).unwrap_or_default();
let _ = results.len();
let type_results = query.query_types(source, Path::new("test.c"));
assert!(!type_results.is_empty());
}
#[test]
fn test_integration_tidy_max_diags_per_file() {
let mut tidy = X86ClangTidy::default();
tidy.max_diagnostics_per_file = 5;
assert_eq!(tidy.max_diagnostics_per_file, 5);
}
#[test]
fn test_integration_tidy_header_filter() {
let mut tidy = X86ClangTidy::default();
tidy.header_filter = Some(".*\\.h$".into());
assert!(tidy.header_filter.is_some());
}
#[test]
fn test_integration_tidy_warnings_as_errors() {
let mut tidy = X86ClangTidy::default();
tidy.warnings_as_errors = true;
assert!(tidy.warnings_as_errors);
}
#[test]
fn test_integration_format_derive_pointer_alignment() {
let mut fmt = X86ClangFormat::default();
fmt.derive_pointer_alignment = true;
assert!(fmt.derive_pointer_alignment);
}
#[test]
fn test_integration_refactor_dry_run() {
let mut tool = X86RefactoringTool::default();
tool.dry_run = true;
assert!(tool.dry_run);
}
#[test]
fn test_integration_query_dump_ast_flag() {
let mut query = X86ClangQuery::new();
query.dump_ast = true;
assert!(query.dump_ast);
}
#[test]
fn test_integration_query_node_filter() {
let mut query = X86ClangQuery::new();
query.node_filter = Some(vec!["function".into(), "variable".into()]);
assert!(query.node_filter.is_some());
let filter = query.node_filter.unwrap();
assert_eq!(filter.len(), 2);
}
#[test]
fn test_integration_query_range_filter() {
let mut query = X86ClangQuery::new();
query.range_filter = Some((1, 1, 10, 80));
assert!(query.range_filter.is_some());
}
#[test]
fn test_integration_query_show_types() {
let mut query = X86ClangQuery::new();
query.show_types = true;
assert!(query.show_types);
}
#[test]
fn test_integration_tooling_config_fields() {
let config = X86ToolingConfig {
compilation_database: Some(PathBuf::from("compile_commands.json")),
clang_format_file: Some(PathBuf::from(".clang-format")),
clang_tidy_file: Some(PathBuf::from(".clang-tidy")),
build_path: Some(PathBuf::from("build")),
source_root: Some(PathBuf::from("src")),
extra_args: vec!["-std=c++17".into()],
jobs: 8,
use_color: false,
quiet: true,
};
assert_eq!(config.jobs, 8);
assert!(!config.use_color);
assert!(config.quiet);
assert!(config.compilation_database.is_some());
}
#[test]
fn test_integration_all_inline_kinds() {
let kinds = [InlineKind::Function, InlineKind::Variable];
for kind in &kinds {
let s = kind.to_string();
assert!(!s.is_empty());
}
}
#[test]
fn test_integration_include_category_ordering() {
assert!(X86IncludeCategory::MainModuleHeader < X86IncludeCategory::LocalHeader);
assert!(X86IncludeCategory::LocalHeader < X86IncludeCategory::ProjectHeader);
assert!(X86IncludeCategory::ProjectHeader < X86IncludeCategory::CSystemHeader);
assert!(X86IncludeCategory::CSystemHeader < X86IncludeCategory::CppSystemHeader);
}
#[test]
fn test_integration_refactor_replacement_line_boundary() {
let repl = X86RefactorReplacement::new(
PathBuf::from("test.c"),
1,
1,
1,
1,
"// prefixed ",
"add prefix",
);
let result = repl.apply("int x = 5;\n");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "// prefixed int x = 5;\n");
}
#[test]
fn test_integration_token_annotator_line_with_preprocessor() {
let annotator = X86TokenAnnotator::default();
let tokens = annotator.annotate_line("#define FOO 123");
let annotations: Vec<X86TokenAnnotation> = tokens.iter().map(|(_, a)| *a).collect();
assert!(annotations.contains(&X86TokenAnnotation::Preprocessor));
}
#[test]
fn test_integration_break_optimizer_all_punctuation_breaks() {
let mut opt = X86LineBreakOptimizer::default();
opt.style.column_limit = 30;
let line = "call(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)";
let result = opt.find_optimal_break(line);
assert!(result.is_some());
}
#[test]
fn test_integration_rewriter_unified_diff_with_changes() {
let mut rw = X86SourceRewriter::new(PathBuf::from("test.c"), "line1\nline2\nline3\n");
let edit = X86SourceEdit::replacement(0, 5, "LINE1", "uppercase");
rw.apply_edit(&edit).unwrap();
let diff = rw.unified_diff();
assert!(diff.contains("+"));
assert!(diff.contains("-"));
}
#[test]
fn test_format_block_comment_multiline_formatting() {
let fmt = X86ClangFormat::default();
let input = "/*\n This is a long\n block comment\n*/\n";
let result = fmt.format_source(input, Path::new("test.c")).unwrap();
assert!(result.contains("/*"));
assert!(result.contains("*/"));
}
#[test]
fn test_format_source_with_macros() {
let fmt = X86ClangFormat::default();
let input = "#define PI 3.14159\nint main() { return 0; }\n";
let result = fmt.format_source(input, Path::new("test.c")).unwrap();
assert!(result.contains("#define"));
assert!(result.contains("PI"));
}
#[test]
fn test_format_source_with_multiple_blank_lines() {
let fmt = X86ClangFormat::default();
let input = "int a;\n\n\n\n\nint b;\n";
let result = fmt.format_source(input, Path::new("test.c")).unwrap();
let blank_count = result.lines().filter(|l| l.trim().is_empty()).count();
assert!(
blank_count <= 2,
"Expected <= 2 blank lines, got {}",
blank_count
);
}
#[test]
fn test_format_block_with_trailing_newline() {
let fmt = X86ClangFormat::default();
let result = fmt.format_block("int x = 5;").unwrap();
assert!(result.ends_with('\n'));
}
#[test]
fn test_tidy_check_use_equals_default_empty_ctor() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_use_equals_default(" {}", 1, Path::new("test.c"));
assert!(!diags.is_empty());
assert_eq!(diags[0].fixit.as_deref(), Some("= default;"));
}
#[test]
fn test_tidy_check_use_equals_delete_private() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_use_equals_delete("private:", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_braces_around_stmts_if_no_brace() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_braces_around_stmts("if (x) return;", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_simplify_bool_ternary() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_simplify_bool("return x ? true : false;", 1, Path::new("test.c"));
assert!(!diags.is_empty());
}
#[test]
fn test_tidy_check_redundant_cstr_with_fprintf() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_redundant_cstr(
"fprintf(stderr, \"%s\", s.c_str());",
1,
Path::new("test.c"),
);
assert!(diags.is_empty());
}
#[test]
fn test_tidy_check_suspicious_semicolon_while() {
let tidy = X86ClangTidy::default();
let diags = tidy.check_suspicious_semicolon("while (x > 0);", 1, Path::new("test.c"));
assert!(diags.is_empty());
}
#[test]
fn test_query_line_matches_for_loop() {
let query = X86ClangQuery::new();
assert!(query.line_matches_kind("for (int i = 0; i < 10; i++)", "for"));
assert!(query.line_matches_kind("for(;;)", "for"));
}
#[test]
fn test_query_line_matches_while_loop() {
let query = X86ClangQuery::new();
assert!(query.line_matches_kind("while (running)", "while"));
}
#[test]
fn test_query_extract_name_no_parens() {
let query = X86ClangQuery::new();
assert_eq!(query.extract_name("int x;", "variable"), "x");
}
#[test]
fn test_query_count_params_nested() {
let query = X86ClangQuery::new();
let count = query.count_params("func(a, inner(b, c), d)");
assert_eq!(count, 4); }
#[test]
fn test_query_match_new_with_bindings() {
let mut m = X86QueryMatch::new(
"call",
Some("printf"),
PathBuf::from("test.c"),
1,
1,
1,
15,
"printf(\"hi\");",
);
m.add_binding("callee", "printf");
m.add_binding("arg", "\"hi\"");
assert_eq!(m.bindings.len(), 2);
}
#[test]
fn test_rewriter_from_file_nonexistent() {
let result = X86SourceRewriter::from_file("/nonexistent/path/file.c");
assert!(result.is_err());
}
#[test]
fn test_rewriter_commit_to_disk() {
use std::io::Write;
let dir = std::env::temp_dir();
let file_path = dir.join("test_rewriter_commit.c");
let _ = fs::write(&file_path, "original\n");
let mut rw = X86SourceRewriter::from_file(&file_path).unwrap();
let edit = X86SourceEdit::replacement(0, 8, "modified", "change");
rw.apply_edit(&edit).unwrap();
rw.commit().unwrap();
let content = fs::read_to_string(&file_path).unwrap();
assert_eq!(content, "modified\n");
let _ = fs::remove_file(&file_path);
}
#[test]
fn test_token_annotate_line_with_mixed_operators() {
let annotator = X86TokenAnnotator::default();
let tokens = annotator.annotate_line("a += b * c - d / e;");
assert!(tokens
.iter()
.any(|(_, a)| *a == X86TokenAnnotation::Assignment));
assert!(tokens
.iter()
.any(|(_, a)| *a == X86TokenAnnotation::BinaryOperator));
}
#[test]
fn test_token_annotate_line_with_template() {
let annotator = X86TokenAnnotator::default();
let tokens = annotator.annotate_line("std::vector<int> v;");
let annotations: Vec<X86TokenAnnotation> = tokens.iter().map(|(_, a)| *a).collect();
assert!(!tokens.is_empty());
}
#[test]
fn test_token_annotate_line_char_literal() {
let annotator = X86TokenAnnotator::default();
let tokens = annotator.annotate_line("char c = 'x';");
assert!(!tokens.is_empty());
}
#[test]
fn test_pipeline_report_has_all_sections() {
let mut pipeline = X86ToolingPipeline::new();
pipeline.tools.stats.files_processed = 3;
pipeline.tools.stats.format_changes = 2;
pipeline.tools.stats.tidy_diagnostics = 5;
pipeline.tools.stats.includes_fixed = 1;
pipeline.tools.stats.refactors_applied = 4;
pipeline.tools.stats.queries_run = 8;
let report = pipeline.report();
assert!(report.contains("Files processed"));
assert!(report.contains("Format changes"));
assert!(report.contains("Tidy diagnostics"));
assert!(report.contains("Includes fixed"));
assert!(report.contains("Refactors applied"));
assert!(report.contains("Queries run"));
}
#[test]
fn test_pipeline_report_with_errors() {
let mut pipeline = X86ToolingPipeline::new();
pipeline.consumer.consume(X86TidyDiagnostic::new(
X86TidySeverity::Error,
"error msg",
"chk",
PathBuf::from("f.c"),
1,
1,
));
let report = pipeline.report();
assert!(report.contains("ERRORS: 1"));
}
#[test]
fn test_config_auto_detect_nonexistent() {
let config = X86ToolingConfig::auto_detect();
assert_eq!(config.jobs, 1);
}
#[test]
fn test_tooling_stats_all_fields() {
let mut stats = X86ToolingStats::new();
stats.files_processed = 10;
stats.format_changes = 5;
stats.tidy_checks_run = 50;
stats.tidy_diagnostics = 12;
stats.includes_fixed = 3;
stats.refactors_applied = 7;
stats.queries_run = 20;
stats.errors = 1;
stats.warnings = 8;
stats.total_time_ms = 500;
let summary = stats.summary();
assert!(summary.contains("10"));
assert!(summary.contains("500"));
}
#[test]
fn test_tools_output_dir() {
let mut tools = X86ClangTools::new();
tools.set_output_dir("/tmp/output");
assert_eq!(tools.output_dir, Some(PathBuf::from("/tmp/output")));
}
#[test]
fn test_tools_compilation_db_none() {
let tools = X86ClangTools::new();
assert!(tools.compilation_db.is_none());
}
#[test]
fn test_tools_add_files_empty_list() {
let mut tools = X86ClangTools::new();
let empty: Vec<&str> = Vec::new();
tools.add_files(&empty);
assert!(tools.source_files.is_empty());
}
#[test]
fn test_tools_stats_new_all_zero() {
let stats = X86ToolingStats::new();
assert_eq!(stats.files_processed, 0);
assert_eq!(stats.format_changes, 0);
assert_eq!(stats.tidy_checks_run, 0);
assert_eq!(stats.tidy_diagnostics, 0);
assert_eq!(stats.includes_fixed, 0);
assert_eq!(stats.refactors_applied, 0);
assert_eq!(stats.queries_run, 0);
assert_eq!(stats.errors, 0);
assert_eq!(stats.warnings, 0);
assert_eq!(stats.total_time_ms, 0);
}
#[test]
fn test_format_indent_style_namespace_indent() {
let mut indent = X86IndentStyle::default();
indent.namespace_indent = Some(4);
assert_eq!(indent.namespace_indent, Some(4));
}
#[test]
fn test_format_indent_style_access_specifiers() {
let mut indent = X86IndentStyle::default();
indent.indent_access_specifiers = true;
assert!(indent.indent_access_specifiers);
}
#[test]
fn test_format_line_break_penalties() {
let lb = X86LineBreakStyle {
penalty_excess_character: 1000000,
penalty_break_before_first_call_parameter: 100,
penalty_break_comment: 1000,
penalty_break_before_ternary: 100,
penalty_return_type_on_its_own_line: 60,
..Default::default()
};
assert_eq!(lb.penalty_excess_character, 1000000);
assert_eq!(lb.penalty_break_comment, 1000);
}
#[test]
fn test_format_whitespace_max_empty_lines() {
let mut ws = X86WhitespaceStyle::default();
ws.max_empty_lines_to_keep = 1;
assert_eq!(ws.max_empty_lines_to_keep, 1);
}
#[test]
fn test_format_alignment_trailing_comments_column() {
let mut align = X86AlignmentStyle::default();
align.trailing_comments_column = 40;
assert_eq!(align.trailing_comments_column, 40);
}
}