#[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::io::{BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct X86ToolingLocation {
pub line: usize,
pub column: usize,
}
impl X86ToolingLocation {
pub fn new(line: usize, column: usize) -> Self {
Self { line, column }
}
}
impl fmt::Display for X86ToolingLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct X86ToolingRange {
pub start: X86ToolingLocation,
pub end: X86ToolingLocation,
}
impl X86ToolingRange {
pub fn new(start: X86ToolingLocation, end: X86ToolingLocation) -> Self {
Self { start, end }
}
pub fn contains(&self, loc: X86ToolingLocation) -> bool {
if loc.line < self.start.line || loc.line > self.end.line {
return false;
}
if loc.line == self.start.line && loc.column < self.start.column {
return false;
}
if loc.line == self.end.line && loc.column > self.end.column {
return false;
}
true
}
pub fn overlaps(&self, other: &Self) -> bool {
if self.end.line < other.start.line {
return false;
}
if other.end.line < self.start.line {
return false;
}
if self.end.line == other.start.line && self.end.column < other.start.column {
return false;
}
if other.end.line == self.start.line && other.end.column < self.start.column {
return false;
}
true
}
}
impl fmt::Display for X86ToolingRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{} - {}]", self.start, self.end)
}
}
#[derive(Debug, Clone)]
pub struct X86ToolingReplacement {
pub file_path: PathBuf,
pub range: X86ToolingRange,
pub replacement_text: String,
pub description: String,
}
impl X86ToolingReplacement {
pub fn new(
file_path: PathBuf,
range: X86ToolingRange,
replacement_text: String,
description: String,
) -> Self {
Self {
file_path,
range,
replacement_text,
description,
}
}
pub fn apply(&self, source: &str) -> Option<String> {
let lines: Vec<&str> = source.lines().collect();
let start_line = self.range.start.line.saturating_sub(1);
let end_line = self.range.end.line.saturating_sub(1);
if start_line >= lines.len() || end_line >= lines.len() {
return None;
}
let mut result = String::new();
for (i, line) in lines.iter().enumerate() {
if i < start_line || i > end_line {
result.push_str(line);
result.push('\n');
} else if i == start_line && i == end_line {
let before = &line[..self.range.start.column.saturating_sub(1).min(line.len())];
let after = &line[self.range.end.column.saturating_sub(1).min(line.len())..];
result.push_str(before);
result.push_str(&self.replacement_text);
result.push_str(after);
result.push('\n');
} else if i == start_line {
let before = &line[..self.range.start.column.saturating_sub(1).min(line.len())];
result.push_str(before);
result.push_str(&self.replacement_text);
result.push('\n');
} else if i == end_line {
let after = &line[self.range.end.column.saturating_sub(1).min(line.len())..];
result.push_str(after);
result.push('\n');
}
}
Some(result)
}
pub fn conflicts_with(&self, other: &Self) -> bool {
if self.file_path != other.file_path {
return false;
}
self.range.overlaps(&other.range)
}
}
impl fmt::Display for X86ToolingReplacement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}: {} => '{}' [{}]",
self.file_path.display(),
self.range,
self.replacement_text,
self.description
)
}
}
#[derive(Debug, Clone)]
pub struct X86ToolingDiagnostic {
pub location: X86ToolingLocation,
pub range: Option<X86ToolingRange>,
pub message: String,
pub severity: X86ToolingSeverity,
pub file_path: PathBuf,
pub fixit_hints: Vec<X86ToolingFixIt>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86ToolingSeverity {
Ignored,
Note,
Warning,
Error,
Fatal,
}
impl fmt::Display for X86ToolingSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Ignored => write!(f, "ignored"),
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 X86ToolingFixIt {
pub range: X86ToolingRange,
pub replacement_text: String,
pub description: String,
}
impl X86ToolingFixIt {
pub fn new(range: X86ToolingRange, replacement_text: String, description: String) -> Self {
Self {
range,
replacement_text,
description,
}
}
}
#[derive(Debug, Clone)]
pub struct X86ToolingResult {
pub success: bool,
pub diagnostics: Vec<X86ToolingDiagnostic>,
pub replacements: Vec<X86ToolingReplacement>,
pub files_processed: usize,
pub files_modified: usize,
pub elapsed_ms: u64,
}
impl X86ToolingResult {
pub fn new() -> Self {
Self {
success: true,
diagnostics: Vec::new(),
replacements: Vec::new(),
files_processed: 0,
files_modified: 0,
elapsed_ms: 0,
}
}
pub fn has_errors(&self) -> bool {
self.diagnostics.iter().any(|d| {
matches!(
d.severity,
X86ToolingSeverity::Error | X86ToolingSeverity::Fatal
)
})
}
pub fn warning_count(&self) -> usize {
self.diagnostics
.iter()
.filter(|d| matches!(d.severity, X86ToolingSeverity::Warning))
.count()
}
pub fn error_count(&self) -> usize {
self.diagnostics
.iter()
.filter(|d| {
matches!(
d.severity,
X86ToolingSeverity::Error | X86ToolingSeverity::Fatal
)
})
.count()
}
}
impl Default for X86ToolingResult {
fn default() -> Self {
Self::new()
}
}
pub struct X86ToolingFull {
pub lib_tooling: X86LibTooling,
pub ast_matchers: X86ASTMatchers,
pub refactoring_tool: X86RefactoringTool,
pub find_symbol: X86ToolingFindSymbol,
pub code_quality: X86ToolingCodeQuality,
pub working_dir: PathBuf,
pub source_files: Vec<PathBuf>,
pub verbose: bool,
pub compilation_db_path: Option<PathBuf>,
pub results: Vec<X86ToolingResult>,
}
impl X86ToolingFull {
pub fn new() -> Self {
Self {
lib_tooling: X86LibTooling::new(),
ast_matchers: X86ASTMatchers::new(),
refactoring_tool: X86RefactoringTool::new(),
find_symbol: X86ToolingFindSymbol::new(),
code_quality: X86ToolingCodeQuality::new(),
working_dir: PathBuf::from("."),
source_files: Vec::new(),
verbose: false,
compilation_db_path: None,
results: Vec::new(),
}
}
pub fn with_working_dir(mut self, dir: PathBuf) -> Self {
self.working_dir = dir;
self
}
pub fn add_file(&mut self, path: PathBuf) {
self.source_files.push(path);
}
pub fn with_compilation_db(mut self, path: PathBuf) -> Self {
self.compilation_db_path = Some(path);
self
}
pub fn with_verbose(mut self, verbose: bool) -> Self {
self.verbose = verbose;
self.lib_tooling.verbose = verbose;
self.ast_matchers.verbose = verbose;
self.refactoring_tool.verbose = verbose;
self.find_symbol.verbose = verbose;
self.code_quality.verbose = verbose;
self
}
pub fn run_all(&mut self) -> Vec<X86ToolingResult> {
if self.verbose {
eprintln!(
"[X86ToolingFull] Running full tooling pipeline on {} files",
self.source_files.len()
);
}
self.results.clear();
let mut db_result = X86ToolingResult::new();
if let Some(ref db_path) = self.compilation_db_path {
db_result = self.lib_tooling.load_compilation_database(db_path);
}
self.results.push(db_result);
for file in &self.source_files.clone() {
let match_result = self.ast_matchers.run_on_file(file);
self.results.push(match_result);
}
let mut refactor_result = X86ToolingResult::new();
refactor_result = self.refactoring_tool.execute_all();
self.results.push(refactor_result);
for file in &self.source_files.clone() {
let find_result = self.find_symbol.run_on_file(file);
self.results.push(find_result);
}
for file in &self.source_files.clone() {
let quality_result = self.code_quality.analyze_file(file);
self.results.push(quality_result);
}
if self.verbose {
let total = self.results.len();
let errors: usize = self.results.iter().map(|r| r.error_count()).sum();
let warnings: usize = self.results.iter().map(|r| r.warning_count()).sum();
eprintln!(
"[X86ToolingFull] Pipeline complete: {} operations, {} errors, {} warnings",
total, errors, warnings
);
}
self.results.clone()
}
pub fn run_ast_matching(&mut self) -> Vec<X86ToolingResult> {
let mut results = Vec::new();
for file in &self.source_files.clone() {
results.push(self.ast_matchers.run_on_file(file));
}
results
}
pub fn run_refactoring(&mut self) -> X86ToolingResult {
self.refactoring_tool.execute_all()
}
pub fn run_code_quality(&mut self) -> Vec<X86ToolingResult> {
let mut results = Vec::new();
for file in &self.source_files.clone() {
results.push(self.code_quality.analyze_file(file));
}
results
}
pub fn generate_summary(&self) -> X86ToolingFullSummary {
X86ToolingFullSummary {
total_files: self.source_files.len(),
total_operations: self.results.len(),
total_errors: self.results.iter().map(|r| r.error_count()).sum(),
total_warnings: self.results.iter().map(|r| r.warning_count()).sum(),
total_replacements: self.results.iter().map(|r| r.replacements.len()).sum(),
files_modified: self.results.iter().map(|r| r.files_modified).sum(),
}
}
}
impl Default for X86ToolingFull {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86ToolingFullSummary {
pub total_files: usize,
pub total_operations: usize,
pub total_errors: usize,
pub total_warnings: usize,
pub total_replacements: usize,
pub files_modified: usize,
}
impl fmt::Display for X86ToolingFullSummary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "=== X86 Tooling Full Summary ===")?;
writeln!(f, " Files processed: {}", self.total_files)?;
writeln!(f, " Operations run: {}", self.total_operations)?;
writeln!(f, " Errors: {}", self.total_errors)?;
writeln!(f, " Warnings: {}", self.total_warnings)?;
writeln!(f, " Replacements made: {}", self.total_replacements)?;
writeln!(f, " Files modified: {}", self.files_modified)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86FrontendActionKind {
SyntaxOnly,
ASTBuild,
EmitLLVM,
EmitAssembly,
EmitObj,
EmitFull,
PluginAction,
Custom,
}
impl fmt::Display for X86FrontendActionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SyntaxOnly => write!(f, "syntax-only"),
Self::ASTBuild => write!(f, "ast-build"),
Self::EmitLLVM => write!(f, "emit-llvm"),
Self::EmitAssembly => write!(f, "emit-assembly"),
Self::EmitObj => write!(f, "emit-obj"),
Self::EmitFull => write!(f, "emit-full"),
Self::PluginAction => write!(f, "plugin-action"),
Self::Custom => write!(f, "custom"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86FrontendAction {
pub kind: X86FrontendActionKind,
pub name: String,
pub source_file: PathBuf,
pub compiler_args: Vec<String>,
}
impl X86FrontendAction {
pub fn new(kind: X86FrontendActionKind, source_file: PathBuf) -> Self {
Self {
kind,
name: kind.to_string(),
source_file,
compiler_args: Vec::new(),
}
}
pub fn with_args(mut self, args: Vec<String>) -> Self {
self.compiler_args = args;
self
}
pub fn syntax_only(source_file: PathBuf) -> Self {
Self::new(X86FrontendActionKind::SyntaxOnly, source_file)
}
pub fn plugin_action(name: &str, source_file: PathBuf) -> Self {
Self {
kind: X86FrontendActionKind::PluginAction,
name: name.to_string(),
source_file,
compiler_args: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86CompilationDBEntry {
pub directory: PathBuf,
pub file: PathBuf,
pub command: String,
pub arguments: Vec<String>,
pub output: Option<PathBuf>,
}
impl X86CompilationDBEntry {
pub fn new(directory: PathBuf, file: PathBuf) -> Self {
Self {
directory,
file,
command: String::new(),
arguments: Vec::new(),
output: None,
}
}
}
#[derive(Debug, Clone)]
pub struct X86CompilationDatabase {
pub entries: Vec<X86CompilationDBEntry>,
pub db_path: PathBuf,
pub loaded: bool,
}
impl X86CompilationDatabase {
pub fn new() -> Self {
Self {
entries: Vec::new(),
db_path: PathBuf::new(),
loaded: false,
}
}
pub fn load_from_file(&mut self, path: &Path) -> Result<(), String> {
let content = fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
self.db_path = path.to_path_buf();
self.entries = self.parse_json_compile_commands(&content);
self.loaded = true;
Ok(())
}
fn parse_json_compile_commands(&self, content: &str) -> Vec<X86CompilationDBEntry> {
let mut entries = Vec::new();
let content = content.trim();
if !content.starts_with('[') {
return entries;
}
let inner = &content[1..content.len() - 1];
let mut depth: usize = 0;
let mut obj_start = None;
for (i, ch) in inner.char_indices() {
match ch {
'{' => {
if depth == 0 {
obj_start = Some(i);
}
depth += 1;
}
'}' => {
depth -= 1;
if depth == 0 {
if let Some(start) = obj_start {
let obj_str = &inner[start..=i];
if let Some(entry) = self.parse_single_command_entry(obj_str) {
entries.push(entry);
}
}
obj_start = None;
}
}
_ => {}
}
}
entries
}
fn parse_single_command_entry(&self, obj: &str) -> Option<X86CompilationDBEntry> {
let directory = self.extract_json_string(obj, "directory")?;
let file = self.extract_json_string(obj, "file")?;
let command = self.extract_json_string(obj, "command").unwrap_or_default();
let arguments = self.extract_json_string_array(obj, "arguments");
let output = self.extract_json_string(obj, "output");
let mut entry = X86CompilationDBEntry::new(PathBuf::from(directory), PathBuf::from(file));
entry.command = command;
entry.arguments = arguments.unwrap_or_default();
entry.output = output.map(PathBuf::from);
Some(entry)
}
fn extract_json_string(&self, json: &str, key: &str) -> Option<String> {
let search = format!("\"{}\"", key);
let pos = json.find(&search)?;
let after_key = &json[pos + search.len()..];
let colon_pos = after_key.find(':')?;
let after_colon = after_key[colon_pos + 1..].trim();
if after_colon.starts_with('"') {
let mut result = String::new();
let chars: Vec<char> = after_colon[1..].chars().collect();
let mut i = 0;
while i < chars.len() {
if chars[i] == '\\' && i + 1 < chars.len() {
i += 1;
match chars[i] {
'"' => result.push('"'),
'\\' => result.push('\\'),
'/' => result.push('/'),
'n' => result.push('\n'),
't' => result.push('\t'),
'r' => result.push('\r'),
_ => {
result.push('\\');
result.push(chars[i]);
}
}
} else if chars[i] == '"' {
return Some(result);
} else {
result.push(chars[i]);
}
i += 1;
}
Some(result)
} else {
None
}
}
fn extract_json_string_array(&self, json: &str, key: &str) -> Option<Vec<String>> {
let search = format!("\"{}\"", key);
let pos = json.find(&search)?;
let after_key = &json[pos + search.len()..];
let colon_pos = after_key.find(':')?;
let after_colon = after_key[colon_pos + 1..].trim();
if !after_colon.starts_with('[') {
return None;
}
let mut result = Vec::new();
let mut i = 1;
while i < after_colon.len() && !after_colon[i..].starts_with(']') {
if after_colon[i..].starts_with('"') {
i += 1;
let mut s = String::new();
while i < after_colon.len() {
let ch = after_colon.as_bytes()[i] as char;
if ch == '\\' && i + 1 < after_colon.len() {
i += 1;
} else if ch == '"' {
break;
} else {
s.push(ch);
}
i += 1;
}
result.push(s);
}
i += 1;
}
if result.is_empty() {
None
} else {
Some(result)
}
}
pub fn lookup(&self, file: &Path) -> Option<&X86CompilationDBEntry> {
let file_name = file.file_name()?;
self.entries
.iter()
.find(|e| e.file.file_name().map_or(false, |f| f == file_name) || e.file == *file)
}
pub fn collect_include_paths(&self) -> Vec<PathBuf> {
let mut paths = BTreeSet::new();
for entry in &self.entries {
let args = if !entry.arguments.is_empty() {
&entry.arguments
} else if !entry.command.is_empty() {
continue;
} else {
continue;
};
let mut i = 0;
while i < args.len() {
if args[i] == "-I" && i + 1 < args.len() {
paths.insert(PathBuf::from(&args[i + 1]));
i += 2;
} else if args[i].starts_with("-I") && args[i].len() > 2 {
paths.insert(PathBuf::from(&args[i][2..]));
i += 1;
} else {
i += 1;
}
}
}
paths.into_iter().collect()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn len(&self) -> usize {
self.entries.len()
}
}
impl Default for X86CompilationDatabase {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ToolExecutionMode {
Standalone,
MapReduce,
Parallel,
}
#[derive(Debug, Clone)]
pub struct X86ToolResults {
pub mode: X86ToolExecutionMode,
pub results: Vec<X86ToolingResult>,
pub aggregated: X86ToolingResult,
}
impl X86ToolResults {
pub fn new(mode: X86ToolExecutionMode) -> Self {
Self {
mode,
results: Vec::new(),
aggregated: X86ToolingResult::new(),
}
}
pub fn aggregate(&mut self) {
let mut agg = X86ToolingResult::new();
for r in &self.results {
agg.diagnostics.extend(r.diagnostics.clone());
agg.replacements.extend(r.replacements.clone());
agg.files_processed += r.files_processed;
agg.files_modified += r.files_modified;
agg.elapsed_ms += r.elapsed_ms;
if !r.success {
agg.success = false;
}
}
self.aggregated = agg;
}
pub fn to_json(&self) -> String {
let mut json = String::from("{\n");
json.push_str(&format!(" \"mode\": \"{:?}\",\n", self.mode));
json.push_str(&format!(
" \"total_operations\": {},\n",
self.results.len()
));
json.push_str(&format!(
" \"total_errors\": {},\n",
self.aggregated.error_count()
));
json.push_str(&format!(
" \"total_warnings\": {},\n",
self.aggregated.warning_count()
));
json.push_str(&format!(
" \"files_processed\": {},\n",
self.aggregated.files_processed
));
json.push_str(&format!(
" \"files_modified\": {},\n",
self.aggregated.files_modified
));
json.push_str(&format!(
" \"elapsed_ms\": {}\n",
self.aggregated.elapsed_ms
));
json.push('}');
json
}
}
pub struct X86ToolExecutor {
pub actions: Vec<X86FrontendAction>,
pub compilation_db: X86CompilationDatabase,
pub mode: X86ToolExecutionMode,
pub results: X86ToolResults,
pub verbose: bool,
}
impl X86ToolExecutor {
pub fn new(mode: X86ToolExecutionMode) -> Self {
Self {
actions: Vec::new(),
compilation_db: X86CompilationDatabase::new(),
mode,
results: X86ToolResults::new(mode),
verbose: false,
}
}
pub fn with_db(mut self, db: X86CompilationDatabase) -> Self {
self.compilation_db = db;
self
}
pub fn add_action(&mut self, action: X86FrontendAction) {
self.actions.push(action);
}
pub fn execute(&mut self) -> X86ToolResults {
match self.mode {
X86ToolExecutionMode::Standalone => self.execute_standalone(),
X86ToolExecutionMode::MapReduce => self.execute_map_reduce(),
X86ToolExecutionMode::Parallel => self.execute_parallel(),
}
}
fn execute_standalone(&mut self) -> X86ToolResults {
let mut results = X86ToolResults::new(X86ToolExecutionMode::Standalone);
for action in &self.actions {
let result = self.execute_single_action(action);
results.results.push(result);
}
results.aggregate();
results
}
fn execute_map_reduce(&mut self) -> X86ToolResults {
let mut results = X86ToolResults::new(X86ToolExecutionMode::MapReduce);
let mut map_results = Vec::new();
for action in &self.actions {
map_results.push(self.execute_single_action(action));
}
let mut reduced = X86ToolingResult::new();
for mr in &map_results {
reduced.diagnostics.extend(mr.diagnostics.clone());
reduced.replacements.extend(mr.replacements.clone());
reduced.files_processed += mr.files_processed;
reduced.files_modified += mr.files_modified;
reduced.elapsed_ms += mr.elapsed_ms;
if !mr.success {
reduced.success = false;
}
}
results.results = map_results;
results.aggregated = reduced;
results
}
fn execute_parallel(&mut self) -> X86ToolResults {
let mut results = X86ToolResults::new(X86ToolExecutionMode::Parallel);
for action in &self.actions {
let result = self.execute_single_action(action);
results.results.push(result);
}
results.aggregate();
results
}
fn execute_single_action(&self, action: &X86FrontendAction) -> X86ToolingResult {
let mut result = X86ToolingResult::new();
result.files_processed = 1;
if self.verbose {
eprintln!(
"[ToolExecutor] Executing {} action '{}' on {}",
action.kind,
action.name,
action.source_file.display()
);
}
match action.kind {
X86FrontendActionKind::SyntaxOnly => {
if !action.source_file.exists() {
result.success = false;
result.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(0, 0),
range: None,
message: format!("File not found: {}", action.source_file.display()),
severity: X86ToolingSeverity::Error,
file_path: action.source_file.clone(),
fixit_hints: Vec::new(),
});
} else {
result.success = true;
}
}
X86FrontendActionKind::ASTBuild => {
if let Ok(_content) = fs::read_to_string(&action.source_file) {
result.success = true;
} else {
result.success = false;
result.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(0, 0),
range: None,
message: format!("Failed to read: {}", action.source_file.display()),
severity: X86ToolingSeverity::Error,
file_path: action.source_file.clone(),
fixit_hints: Vec::new(),
});
}
}
_ => {
result.success = true;
}
}
result
}
}
pub struct X86LibTooling {
pub compilation_db: X86CompilationDatabase,
pub executor: X86ToolExecutor,
pub frontend_actions: Vec<X86FrontendAction>,
pub tool_results: Vec<X86ToolResults>,
pub verbose: bool,
}
impl X86LibTooling {
pub fn new() -> Self {
Self {
compilation_db: X86CompilationDatabase::new(),
executor: X86ToolExecutor::new(X86ToolExecutionMode::Standalone),
frontend_actions: Vec::new(),
tool_results: Vec::new(),
verbose: false,
}
}
pub fn load_compilation_database(&mut self, path: &Path) -> X86ToolingResult {
let mut result = X86ToolingResult::new();
match self.compilation_db.load_from_file(path) {
Ok(()) => {
result.success = true;
if self.verbose {
eprintln!(
"[X86LibTooling] Loaded compilation database with {} entries",
self.compilation_db.len()
);
}
}
Err(e) => {
result.success = false;
result.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(0, 0),
range: None,
message: e,
severity: X86ToolingSeverity::Error,
file_path: path.to_path_buf(),
fixit_hints: Vec::new(),
});
}
}
result
}
pub fn add_frontend_action(&mut self, action: X86FrontendAction) {
self.frontend_actions.push(action);
}
pub fn run(&mut self) -> X86ToolResults {
self.executor.actions = self.frontend_actions.clone();
self.executor.compilation_db = self.compilation_db.clone();
let results = self.executor.execute();
self.tool_results.push(results.clone());
results
}
pub fn run_action(&mut self, action: X86FrontendAction) -> X86ToolingResult {
let mut result = X86ToolingResult::new();
result.files_processed = 1;
if !action.source_file.exists() {
result.success = false;
result.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(0, 0),
range: None,
message: format!("File not found: {}", action.source_file.display()),
severity: X86ToolingSeverity::Error,
file_path: action.source_file.clone(),
fixit_hints: Vec::new(),
});
return result;
}
match action.kind {
X86FrontendActionKind::SyntaxOnly => {
result.success = true;
}
X86FrontendActionKind::ASTBuild => {
result.success = true;
}
_ => {
result.success = true;
}
}
result
}
pub fn get_compilation_db(&self) -> &X86CompilationDatabase {
&self.compilation_db
}
pub fn set_execution_mode(&mut self, mode: X86ToolExecutionMode) {
self.executor.mode = mode;
}
pub fn create_syntax_action(&self, file: &Path) -> X86FrontendAction {
let mut action = X86FrontendAction::syntax_only(file.to_path_buf());
if let Some(entry) = self.compilation_db.lookup(file) {
action.compiler_args = entry.arguments.clone();
}
action
}
pub fn create_plugin_action(&self, name: &str, file: &Path) -> X86FrontendAction {
let mut action = X86FrontendAction::plugin_action(name, file.to_path_buf());
if let Some(entry) = self.compilation_db.lookup(file) {
action.compiler_args = entry.arguments.clone();
}
action
}
}
impl Default for X86LibTooling {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86MatchedNodeKind {
FunctionDecl,
VarDecl,
RecordDecl,
EnumDecl,
CXXRecordDecl,
ClassTemplateDecl,
NamespaceDecl,
UsingDecl,
TypedefDecl,
FieldDecl,
ParmVarDecl,
CXXMethodDecl,
CXXConstructorDecl,
CXXDestructorDecl,
FunctionTemplateDecl,
ClassTemplateSpecializationDecl,
CallExpr,
CXXMemberCallExpr,
CXXOperatorCallExpr,
DeclRefExpr,
MemberExpr,
IntegerLiteral,
FloatLiteral,
CharacterLiteral,
StringLiteral,
BooleanLiteral,
NullPtrLiteral,
UnaryOperator,
BinaryOperator,
ConditionalOperator,
CastExpr,
ExplicitCastExpr,
CStyleCastExpr,
StaticCastExpr,
DynamicCastExpr,
ConstCastExpr,
ReinterpretCastExpr,
ImplicitCastExpr,
MaterializeTemporaryExpr,
ArraySubscriptExpr,
InitListExpr,
ParenListExpr,
LambdaExpr,
NewExpr,
DeleteExpr,
ThisExpr,
DefaultArgExpr,
CXXConstructExpr,
CXXTemporaryObjectExpr,
CXXBindTemporaryExpr,
CXXFunctionalCastExpr,
IfStmt,
ForStmt,
WhileStmt,
DoStmt,
SwitchStmt,
ReturnStmt,
BreakStmt,
ContinueStmt,
GotoStmt,
CompoundStmt,
DeclStmt,
NullStmt,
CaseStmt,
DefaultStmt,
LabelStmt,
CXXTryStmt,
CXXCatchStmt,
CXXThrowExpr,
CXXForRangeStmt,
PointerType,
ReferenceType,
ArrayType,
ElaboratedType,
TypedefType,
TemplateSpecializationType,
AutoType,
DecayedType,
ParenType,
AttributedType,
Any,
}
impl fmt::Display for X86MatchedNodeKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::FunctionDecl => "functionDecl",
Self::VarDecl => "varDecl",
Self::RecordDecl => "recordDecl",
Self::EnumDecl => "enumDecl",
Self::CXXRecordDecl => "cxxRecordDecl",
Self::ClassTemplateDecl => "classTemplateDecl",
Self::NamespaceDecl => "namespaceDecl",
Self::UsingDecl => "usingDecl",
Self::TypedefDecl => "typedefDecl",
Self::FieldDecl => "fieldDecl",
Self::ParmVarDecl => "parmVarDecl",
Self::CXXMethodDecl => "cxxMethodDecl",
Self::CXXConstructorDecl => "cxxConstructorDecl",
Self::CXXDestructorDecl => "cxxDestructorDecl",
Self::FunctionTemplateDecl => "functionTemplateDecl",
Self::ClassTemplateSpecializationDecl => "classTemplateSpecializationDecl",
Self::CallExpr => "callExpr",
Self::CXXMemberCallExpr => "cxxMemberCallExpr",
Self::CXXOperatorCallExpr => "cxxOperatorCallExpr",
Self::DeclRefExpr => "declRefExpr",
Self::MemberExpr => "memberExpr",
Self::IntegerLiteral => "integerLiteral",
Self::FloatLiteral => "floatLiteral",
Self::CharacterLiteral => "characterLiteral",
Self::StringLiteral => "stringLiteral",
Self::BooleanLiteral => "booleanLiteral",
Self::NullPtrLiteral => "nullptrLiteral",
Self::UnaryOperator => "unaryOperator",
Self::BinaryOperator => "binaryOperator",
Self::ConditionalOperator => "conditionalOperator",
Self::CastExpr => "castExpr",
Self::ExplicitCastExpr => "explicitCastExpr",
Self::CStyleCastExpr => "cStyleCastExpr",
Self::StaticCastExpr => "staticCastExpr",
Self::DynamicCastExpr => "dynamicCastExpr",
Self::ConstCastExpr => "constCastExpr",
Self::ReinterpretCastExpr => "reinterpretCastExpr",
Self::ImplicitCastExpr => "implicitCastExpr",
Self::MaterializeTemporaryExpr => "materializeTemporaryExpr",
Self::ArraySubscriptExpr => "arraySubscriptExpr",
Self::InitListExpr => "initListExpr",
Self::ParenListExpr => "parenListExpr",
Self::LambdaExpr => "lambdaExpr",
Self::NewExpr => "newExpr",
Self::DeleteExpr => "deleteExpr",
Self::ThisExpr => "thisExpr",
Self::DefaultArgExpr => "defaultArgExpr",
Self::CXXConstructExpr => "cxxConstructExpr",
Self::CXXTemporaryObjectExpr => "cxxTemporaryObjectExpr",
Self::CXXBindTemporaryExpr => "cxxBindTemporaryExpr",
Self::CXXFunctionalCastExpr => "cxxFunctionalCastExpr",
Self::IfStmt => "ifStmt",
Self::ForStmt => "forStmt",
Self::WhileStmt => "whileStmt",
Self::DoStmt => "doStmt",
Self::SwitchStmt => "switchStmt",
Self::ReturnStmt => "returnStmt",
Self::BreakStmt => "breakStmt",
Self::ContinueStmt => "continueStmt",
Self::GotoStmt => "gotoStmt",
Self::CompoundStmt => "compoundStmt",
Self::DeclStmt => "declStmt",
Self::NullStmt => "nullStmt",
Self::CaseStmt => "caseStmt",
Self::DefaultStmt => "defaultStmt",
Self::LabelStmt => "labelStmt",
Self::CXXTryStmt => "cxxTryStmt",
Self::CXXCatchStmt => "cxxCatchStmt",
Self::CXXThrowExpr => "cxxThrowExpr",
Self::CXXForRangeStmt => "cxxForRangeStmt",
Self::PointerType => "pointerType",
Self::ReferenceType => "referenceType",
Self::ArrayType => "arrayType",
Self::ElaboratedType => "elaboratedType",
Self::TypedefType => "typedefType",
Self::TemplateSpecializationType => "templateSpecializationType",
Self::AutoType => "autoType",
Self::DecayedType => "decayedType",
Self::ParenType => "parenType",
Self::AttributedType => "attributedType",
Self::Any => "any",
};
write!(f, "{}", s)
}
}
#[derive(Debug, Clone)]
pub enum X86ASTMatcherPredicate {
NodeKind(X86MatchedNodeKind),
HasName(String),
MatchesName(String),
HasType(String),
HasReturnType(String),
IsDefinition,
IsExplicit,
IsVirtual,
IsOverride,
IsFinal,
IsConst,
IsVolatile,
IsStatic,
IsInline,
IsPublic,
IsProtected,
IsPrivate,
HasAncestor(Box<X86ASTMatcherPredicate>),
HasDescendant(Box<X86ASTMatcherPredicate>),
HasParent(Box<X86ASTMatcherPredicate>),
HasInitializer(Box<X86ASTMatcherPredicate>),
HasBody(Box<X86ASTMatcherPredicate>),
IsExpandedFromMacro,
IsImplicit,
IsDefaulted,
IsDeleted,
IsNoThrow,
IsConstexpr,
IsMutable,
HasParameter(usize, Box<X86ASTMatcherPredicate>),
HasAnyParameter(Box<X86ASTMatcherPredicate>),
AllOf(Vec<X86ASTMatcherPredicate>),
AnyOf(Vec<X86ASTMatcherPredicate>),
Unless(Box<X86ASTMatcherPredicate>),
Optionally(Box<X86ASTMatcherPredicate>),
Has(Box<X86ASTMatcherPredicate>),
ForEach(Box<X86ASTMatcherPredicate>),
ForEachDescendant(Box<X86ASTMatcherPredicate>),
FindAll(Box<X86ASTMatcherPredicate>),
Anything,
EqualsNode(String),
AsString(String),
IsInteger,
IsFloating,
IsPointer,
IsReference,
IsConstQualified,
IsVolatileQualified,
HasCanonicalType(Box<X86ASTMatcherPredicate>),
HasStringValue(String),
HasIntegerValue(i64),
}
impl X86ASTMatcherPredicate {
pub fn to_query_string(&self) -> String {
match self {
Self::NodeKind(k) => k.to_string(),
Self::HasName(n) => format!("hasName(\"{}\")", n),
Self::MatchesName(p) => format!("matchesName(\"{}\")", p),
Self::HasType(t) => format!("hasType(\"{}\")", t),
Self::HasReturnType(t) => format!("hasReturnType(\"{}\")", t),
Self::IsDefinition => "isDefinition()".into(),
Self::IsExplicit => "isExplicit()".into(),
Self::IsVirtual => "isVirtual()".into(),
Self::IsOverride => "isOverride()".into(),
Self::IsFinal => "isFinal()".into(),
Self::IsConst => "isConst()".into(),
Self::IsVolatile => "isVolatile()".into(),
Self::IsStatic => "isStatic()".into(),
Self::IsInline => "isInline()".into(),
Self::IsPublic => "isPublic()".into(),
Self::IsProtected => "isProtected()".into(),
Self::IsPrivate => "isPrivate()".into(),
Self::HasAncestor(m) => format!("hasAncestor({})", m.to_query_string()),
Self::HasDescendant(m) => format!("hasDescendant({})", m.to_query_string()),
Self::HasParent(m) => format!("hasParent({})", m.to_query_string()),
Self::HasInitializer(m) => format!("hasInitializer({})", m.to_query_string()),
Self::HasBody(m) => format!("hasBody({})", m.to_query_string()),
Self::IsExpandedFromMacro => "isExpandedFromMacro()".into(),
Self::IsImplicit => "isImplicit()".into(),
Self::IsDefaulted => "isDefaulted()".into(),
Self::IsDeleted => "isDeleted()".into(),
Self::IsNoThrow => "isNoThrow()".into(),
Self::IsConstexpr => "isConstexpr()".into(),
Self::IsMutable => "isMutable()".into(),
Self::HasParameter(i, m) => format!("hasParameter({}, {})", i, m.to_query_string()),
Self::HasAnyParameter(m) => format!("hasAnyParameter({})", m.to_query_string()),
Self::AllOf(matchers) => {
let inner: Vec<String> = matchers.iter().map(|m| m.to_query_string()).collect();
format!("allOf({})", inner.join(", "))
}
Self::AnyOf(matchers) => {
let inner: Vec<String> = matchers.iter().map(|m| m.to_query_string()).collect();
format!("anyOf({})", inner.join(", "))
}
Self::Unless(m) => format!("unless({})", m.to_query_string()),
Self::Optionally(m) => format!("optionally({})", m.to_query_string()),
Self::Has(m) => format!("has({})", m.to_query_string()),
Self::ForEach(m) => format!("forEach({})", m.to_query_string()),
Self::ForEachDescendant(m) => format!("forEachDescendant({})", m.to_query_string()),
Self::FindAll(m) => format!("findAll({})", m.to_query_string()),
Self::Anything => "anything()".into(),
Self::EqualsNode(n) => format!("equalsNode(\"{}\")", n),
Self::AsString(s) => format!("asString(\"{}\")", s),
Self::IsInteger => "isInteger()".into(),
Self::IsFloating => "isFloating()".into(),
Self::IsPointer => "isPointer()".into(),
Self::IsReference => "isReference()".into(),
Self::IsConstQualified => "isConstQualified()".into(),
Self::IsVolatileQualified => "isVolatileQualified()".into(),
Self::HasCanonicalType(m) => format!("hasCanonicalType({})", m.to_query_string()),
Self::HasStringValue(v) => format!("hasStringValue(\"{}\")", v),
Self::HasIntegerValue(v) => format!("hasIntegerValue({})", v),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86MatchKind {
Direct,
Descent,
Ascent,
}
#[derive(Debug, Clone)]
pub struct X86ASTMatch {
pub node_kind: X86MatchedNodeKind,
pub name: Option<String>,
pub location: X86ToolingLocation,
pub range: Option<X86ToolingRange>,
pub match_kind: X86MatchKind,
pub file_path: PathBuf,
pub bindings: HashMap<String, String>,
}
impl X86ASTMatch {
pub fn new(
node_kind: X86MatchedNodeKind,
location: X86ToolingLocation,
file_path: PathBuf,
) -> Self {
Self {
node_kind,
name: None,
location,
range: None,
match_kind: X86MatchKind::Direct,
file_path,
bindings: HashMap::new(),
}
}
pub fn with_name(mut self, name: String) -> Self {
self.name = Some(name);
self
}
pub fn with_range(mut self, range: X86ToolingRange) -> Self {
self.range = Some(range);
self
}
pub fn with_binding(mut self, key: &str, value: &str) -> Self {
self.bindings.insert(key.to_string(), value.to_string());
self
}
}
impl fmt::Display for X86ASTMatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.node_kind)?;
if let Some(ref name) = self.name {
write!(f, " '{}'", name)?;
}
write!(f, " at {}", self.location)?;
if let Some(ref range) = self.range {
write!(f, " {}", range)?;
}
if !self.bindings.is_empty() {
write!(f, " bindings: [")?;
for (i, (k, v)) in self.bindings.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}={}", k, v)?;
}
write!(f, "]")?;
}
Ok(())
}
}
pub struct X86ASTMatchers {
pub matchers: Vec<X86ASTMatcherPredicate>,
pub matches: Vec<X86ASTMatch>,
source_cache: HashMap<PathBuf, String>,
pub verbose: bool,
}
impl X86ASTMatchers {
pub fn new() -> Self {
Self {
matchers: Vec::new(),
matches: Vec::new(),
source_cache: HashMap::new(),
verbose: false,
}
}
pub fn function_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::FunctionDecl)
}
pub fn var_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::VarDecl)
}
pub fn record_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::RecordDecl)
}
pub fn enum_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::EnumDecl)
}
pub fn cxx_record_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXRecordDecl)
}
pub fn class_template_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ClassTemplateDecl)
}
pub fn namespace_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::NamespaceDecl)
}
pub fn using_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::UsingDecl)
}
pub fn typedef_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::TypedefDecl)
}
pub fn field_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::FieldDecl)
}
pub fn parm_var_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ParmVarDecl)
}
pub fn cxx_method_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXMethodDecl)
}
pub fn cxx_constructor_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXConstructorDecl)
}
pub fn cxx_destructor_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXDestructorDecl)
}
pub fn function_template_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::FunctionTemplateDecl)
}
pub fn class_template_specialization_decl() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ClassTemplateSpecializationDecl)
}
pub fn is_definition() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsDefinition
}
pub fn is_explicit() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsExplicit
}
pub fn is_virtual() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsVirtual
}
pub fn is_override() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsOverride
}
pub fn is_final() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsFinal
}
pub fn is_const() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsConst
}
pub fn is_volatile() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsVolatile
}
pub fn is_static() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsStatic
}
pub fn is_inline() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsInline
}
pub fn is_public() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsPublic
}
pub fn is_protected() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsProtected
}
pub fn is_private() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsPrivate
}
pub fn has_name(name: &str) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::HasName(name.to_string())
}
pub fn matches_name(pattern: &str) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::MatchesName(pattern.to_string())
}
pub fn has_type(ty: &str) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::HasType(ty.to_string())
}
pub fn has_return_type(ty: &str) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::HasReturnType(ty.to_string())
}
pub fn has_ancestor(matcher: X86ASTMatcherPredicate) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::HasAncestor(Box::new(matcher))
}
pub fn has_descendant(matcher: X86ASTMatcherPredicate) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::HasDescendant(Box::new(matcher))
}
pub fn has_parent(matcher: X86ASTMatcherPredicate) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::HasParent(Box::new(matcher))
}
pub fn has_initializer(matcher: X86ASTMatcherPredicate) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::HasInitializer(Box::new(matcher))
}
pub fn has_body(matcher: X86ASTMatcherPredicate) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::HasBody(Box::new(matcher))
}
pub fn has_parameter(index: usize, matcher: X86ASTMatcherPredicate) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::HasParameter(index, Box::new(matcher))
}
pub fn has_any_parameter(matcher: X86ASTMatcherPredicate) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::HasAnyParameter(Box::new(matcher))
}
pub fn is_expanded_from_macro() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsExpandedFromMacro
}
pub fn is_implicit() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsImplicit
}
pub fn is_defaulted() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsDefaulted
}
pub fn is_deleted() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsDeleted
}
pub fn is_no_throw() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsNoThrow
}
pub fn is_constexpr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsConstexpr
}
pub fn is_mutable() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsMutable
}
pub fn call_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CallExpr)
}
pub fn cxx_member_call_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXMemberCallExpr)
}
pub fn cxx_operator_call_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXOperatorCallExpr)
}
pub fn decl_ref_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::DeclRefExpr)
}
pub fn member_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::MemberExpr)
}
pub fn integer_literal() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::IntegerLiteral)
}
pub fn float_literal() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::FloatLiteral)
}
pub fn character_literal() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CharacterLiteral)
}
pub fn string_literal() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::StringLiteral)
}
pub fn boolean_literal() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::BooleanLiteral)
}
pub fn nullptr_literal() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::NullPtrLiteral)
}
pub fn unary_operator() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::UnaryOperator)
}
pub fn binary_operator() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::BinaryOperator)
}
pub fn conditional_operator() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ConditionalOperator)
}
pub fn cast_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CastExpr)
}
pub fn explicit_cast_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ExplicitCastExpr)
}
pub fn c_style_cast_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CStyleCastExpr)
}
pub fn static_cast_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::StaticCastExpr)
}
pub fn dynamic_cast_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::DynamicCastExpr)
}
pub fn const_cast_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ConstCastExpr)
}
pub fn reinterpret_cast_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ReinterpretCastExpr)
}
pub fn implicit_cast_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ImplicitCastExpr)
}
pub fn materialize_temporary_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::MaterializeTemporaryExpr)
}
pub fn array_subscript_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ArraySubscriptExpr)
}
pub fn init_list_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::InitListExpr)
}
pub fn paren_list_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ParenListExpr)
}
pub fn lambda_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::LambdaExpr)
}
pub fn new_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::NewExpr)
}
pub fn delete_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::DeleteExpr)
}
pub fn this_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ThisExpr)
}
pub fn default_arg_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::DefaultArgExpr)
}
pub fn cxx_construct_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXConstructExpr)
}
pub fn cxx_temporary_object_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXTemporaryObjectExpr)
}
pub fn cxx_bind_temporary_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXBindTemporaryExpr)
}
pub fn cxx_functional_cast_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXFunctionalCastExpr)
}
pub fn as_string(s: &str) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::AsString(s.to_string())
}
pub fn is_integer() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsInteger
}
pub fn is_floating() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsFloating
}
pub fn is_pointer() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsPointer
}
pub fn is_reference() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsReference
}
pub fn is_const_qualified() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsConstQualified
}
pub fn is_volatile_qualified() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::IsVolatileQualified
}
pub fn pointer_type() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::PointerType)
}
pub fn reference_type() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ReferenceType)
}
pub fn array_type() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ArrayType)
}
pub fn elaborated_type() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ElaboratedType)
}
pub fn typedef_type() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::TypedefType)
}
pub fn template_specialization_type() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::TemplateSpecializationType)
}
pub fn auto_type() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::AutoType)
}
pub fn decayed_type() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::DecayedType)
}
pub fn paren_type() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ParenType)
}
pub fn attributed_type() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::AttributedType)
}
pub fn has_canonical_type(matcher: X86ASTMatcherPredicate) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::HasCanonicalType(Box::new(matcher))
}
pub fn if_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::IfStmt)
}
pub fn for_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ForStmt)
}
pub fn while_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::WhileStmt)
}
pub fn do_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::DoStmt)
}
pub fn switch_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::SwitchStmt)
}
pub fn return_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ReturnStmt)
}
pub fn break_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::BreakStmt)
}
pub fn continue_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ContinueStmt)
}
pub fn goto_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::GotoStmt)
}
pub fn compound_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CompoundStmt)
}
pub fn decl_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::DeclStmt)
}
pub fn null_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::NullStmt)
}
pub fn case_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CaseStmt)
}
pub fn default_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::DefaultStmt)
}
pub fn label_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::LabelStmt)
}
pub fn cxx_try_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXTryStmt)
}
pub fn cxx_catch_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXCatchStmt)
}
pub fn cxx_throw_expr() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXThrowExpr)
}
pub fn cxx_for_range_stmt() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXForRangeStmt)
}
pub fn all_of(matchers: Vec<X86ASTMatcherPredicate>) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::AllOf(matchers)
}
pub fn any_of(matchers: Vec<X86ASTMatcherPredicate>) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::AnyOf(matchers)
}
pub fn unless(matcher: X86ASTMatcherPredicate) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::Unless(Box::new(matcher))
}
pub fn optionally(matcher: X86ASTMatcherPredicate) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::Optionally(Box::new(matcher))
}
pub fn has(matcher: X86ASTMatcherPredicate) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::Has(Box::new(matcher))
}
pub fn for_each(matcher: X86ASTMatcherPredicate) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::ForEach(Box::new(matcher))
}
pub fn for_each_descendant(matcher: X86ASTMatcherPredicate) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::ForEachDescendant(Box::new(matcher))
}
pub fn find_all(matcher: X86ASTMatcherPredicate) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::FindAll(Box::new(matcher))
}
pub fn anything() -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::Anything
}
pub fn equals_node(name: &str) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::EqualsNode(name.to_string())
}
pub fn has_string_value(value: &str) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::HasStringValue(value.to_string())
}
pub fn has_integer_value(value: i64) -> X86ASTMatcherPredicate {
X86ASTMatcherPredicate::HasIntegerValue(value)
}
pub fn add_matcher(&mut self, matcher: X86ASTMatcherPredicate) {
self.matchers.push(matcher);
}
pub fn run_on_file(&mut self, file: &Path) -> X86ToolingResult {
let mut result = X86ToolingResult::new();
result.files_processed = 1;
let source = match fs::read_to_string(file) {
Ok(s) => s,
Err(e) => {
result.success = false;
result.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(0, 0),
range: None,
message: format!("Failed to read {}: {}", file.display(), e),
severity: X86ToolingSeverity::Error,
file_path: file.to_path_buf(),
fixit_hints: Vec::new(),
});
return result;
}
};
self.source_cache.insert(file.to_path_buf(), source.clone());
for matcher in &self.matchers.clone() {
let file_matches = self.match_in_source(&source, matcher, file);
for m in file_matches {
self.matches.push(m);
}
}
result.success = true;
result.files_modified = 0;
result
}
pub fn match_in_source(
&self,
source: &str,
matcher: &X86ASTMatcherPredicate,
file: &Path,
) -> Vec<X86ASTMatch> {
let mut matches = Vec::new();
match matcher {
X86ASTMatcherPredicate::NodeKind(kind) => {
matches.extend(self.match_node_kind_in_source(source, *kind, file));
}
X86ASTMatcherPredicate::HasName(name) => {
let inner = X86ASTMatcherPredicate::Anything;
let decl_matches = self.match_in_source(source, &inner, file);
for m in decl_matches {
if let Some(ref n) = m.name {
if n == name {
matches.push(m);
}
}
}
for (line_num, line) in source.lines().enumerate() {
if self.line_contains_decl_named(line, name) {
let loc = X86ToolingLocation::new(line_num + 1, 1);
let m = X86ASTMatch::new(X86MatchedNodeKind::Any, loc, file.to_path_buf())
.with_name(name.clone());
matches.push(m);
}
}
}
X86ASTMatcherPredicate::MatchesName(pattern) => {
for (line_num, line) in source.lines().enumerate() {
if self.line_matches_name_pattern(line, pattern) {
let loc = X86ToolingLocation::new(line_num + 1, 1);
let m = X86ASTMatch::new(X86MatchedNodeKind::Any, loc, file.to_path_buf());
matches.push(m);
}
}
}
X86ASTMatcherPredicate::HasType(_ty) => {
for (line_num, line) in source.lines().enumerate() {
if self.line_has_type_annotation(line) {
let loc = X86ToolingLocation::new(line_num + 1, 1);
let m = X86ASTMatch::new(X86MatchedNodeKind::Any, loc, file.to_path_buf());
matches.push(m);
}
}
}
X86ASTMatcherPredicate::HasAncestor(inner) => {
let all = self.match_node_kind_in_source(source, X86MatchedNodeKind::Any, file);
let descendant_matches = self.match_in_source(source, inner, file);
for d in &descendant_matches {
for a in &all {
if a.location.line < d.location.line || a.location.line == d.location.line {
let mut m = d.clone();
m.match_kind = X86MatchKind::Ascent;
matches.push(m);
break;
}
}
}
}
X86ASTMatcherPredicate::HasDescendant(inner) => {
let descendant_matches = self.match_in_source(source, inner, file);
for d in descendant_matches {
let mut m = d.clone();
m.match_kind = X86MatchKind::Descent;
matches.push(m);
}
}
X86ASTMatcherPredicate::AllOf(matchers) => {
if matchers.is_empty() {
return matches;
}
let first = self.match_in_source(source, &matchers[0], file);
for m in first {
let mut all_match = true;
for rest in &matchers[1..] {
let rest_matches = self.match_in_source(source, rest, file);
if rest_matches.is_empty() {
all_match = false;
break;
}
}
if all_match {
matches.push(m);
}
}
}
X86ASTMatcherPredicate::AnyOf(matchers) => {
for m in matchers {
let sub = self.match_in_source(source, m, file);
matches.extend(sub);
}
}
X86ASTMatcherPredicate::Unless(inner) => {
let inner_matches = self.match_in_source(source, inner, file);
if inner_matches.is_empty() {
let all = self.match_node_kind_in_source(source, X86MatchedNodeKind::Any, file);
matches.extend(all);
}
}
X86ASTMatcherPredicate::IsDefinition => {
for (line_num, line) in source.lines().enumerate() {
if line.contains('{')
&& (line.contains("void")
|| line.contains("int")
|| line.contains("class")
|| line.contains("struct"))
{
let loc = X86ToolingLocation::new(line_num + 1, 1);
let m = X86ASTMatch::new(X86MatchedNodeKind::Any, loc, file.to_path_buf());
matches.push(m);
}
}
}
X86ASTMatcherPredicate::IsVirtual => {
for (line_num, line) in source.lines().enumerate() {
if line.contains("virtual") {
let loc = X86ToolingLocation::new(line_num + 1, 1);
let m = X86ASTMatch::new(
X86MatchedNodeKind::CXXMethodDecl,
loc,
file.to_path_buf(),
);
matches.push(m);
}
}
}
X86ASTMatcherPredicate::IsStatic => {
for (line_num, line) in source.lines().enumerate() {
if line.trim().starts_with("static ") || line.contains(" static ") {
let loc = X86ToolingLocation::new(line_num + 1, 1);
let m = X86ASTMatch::new(X86MatchedNodeKind::Any, loc, file.to_path_buf());
matches.push(m);
}
}
}
X86ASTMatcherPredicate::IsConst => {
for (line_num, line) in source.lines().enumerate() {
if line.contains("const") && !line.contains("const_cast") {
let loc = X86ToolingLocation::new(line_num + 1, 1);
let m = X86ASTMatch::new(X86MatchedNodeKind::Any, loc, file.to_path_buf());
matches.push(m);
}
}
}
X86ASTMatcherPredicate::IsInline => {
for (line_num, line) in source.lines().enumerate() {
if line.contains("inline") {
let loc = X86ToolingLocation::new(line_num + 1, 1);
let m = X86ASTMatch::new(X86MatchedNodeKind::Any, loc, file.to_path_buf());
matches.push(m);
}
}
}
X86ASTMatcherPredicate::IsConstexpr => {
for (line_num, line) in source.lines().enumerate() {
if line.contains("constexpr") {
let loc = X86ToolingLocation::new(line_num + 1, 1);
let m = X86ASTMatch::new(X86MatchedNodeKind::Any, loc, file.to_path_buf());
matches.push(m);
}
}
}
X86ASTMatcherPredicate::IsInteger => {
for (line_num, line) in source.lines().enumerate() {
if line.contains("int ") || line.contains("long ") || line.contains("short ") {
let loc = X86ToolingLocation::new(line_num + 1, 1);
let m = X86ASTMatch::new(X86MatchedNodeKind::Any, loc, file.to_path_buf());
matches.push(m);
}
}
}
X86ASTMatcherPredicate::HasStringValue(val) => {
for (line_num, line) in source.lines().enumerate() {
if line.contains(&format!("\"{}\"", val)) {
let loc = X86ToolingLocation::new(line_num + 1, 1);
let m = X86ASTMatch::new(
X86MatchedNodeKind::StringLiteral,
loc,
file.to_path_buf(),
);
matches.push(m);
}
}
}
X86ASTMatcherPredicate::HasIntegerValue(val) => {
let val_str = val.to_string();
for (line_num, line) in source.lines().enumerate() {
let trimmed = line.trim();
if trimmed == val_str || trimmed.starts_with(&format!("{} ", val_str)) {
let loc = X86ToolingLocation::new(line_num + 1, 1);
let m = X86ASTMatch::new(
X86MatchedNodeKind::IntegerLiteral,
loc,
file.to_path_buf(),
);
matches.push(m);
}
}
}
_ => {
let kind = match matcher {
X86ASTMatcherPredicate::NodeKind(k) => *k,
_ => X86MatchedNodeKind::Any,
};
matches.extend(self.match_node_kind_in_source(source, kind, file));
}
}
matches
}
fn match_node_kind_in_source(
&self,
source: &str,
kind: X86MatchedNodeKind,
file: &Path,
) -> Vec<X86ASTMatch> {
let mut matches = Vec::new();
let keyword = match kind {
X86MatchedNodeKind::FunctionDecl => Some(("void ", "int ")),
X86MatchedNodeKind::VarDecl => Some(("int ", "float ")),
X86MatchedNodeKind::RecordDecl => Some(("struct ", "union ")),
X86MatchedNodeKind::EnumDecl => Some(("enum ", "enum class ")),
X86MatchedNodeKind::CXXRecordDecl => Some(("class ", "struct ")),
X86MatchedNodeKind::NamespaceDecl => Some(("namespace ", "")),
X86MatchedNodeKind::UsingDecl => Some(("using ", "")),
X86MatchedNodeKind::TypedefDecl => Some(("typedef ", "")),
X86MatchedNodeKind::CallExpr => Some(("", "(")),
X86MatchedNodeKind::IfStmt => Some(("if ", "")),
X86MatchedNodeKind::ForStmt => Some(("for ", "")),
X86MatchedNodeKind::WhileStmt => Some(("while ", "")),
X86MatchedNodeKind::ReturnStmt => Some(("return", "")),
X86MatchedNodeKind::SwitchStmt => Some(("switch ", "")),
_ => None,
};
if let Some((kw1, kw2)) = keyword {
for (line_num, line) in source.lines().enumerate() {
let trimmed = line.trim();
if (!kw1.is_empty() && trimmed.contains(kw1))
|| (!kw2.is_empty() && trimmed.contains(kw2))
{
let loc = X86ToolingLocation::new(line_num + 1, 1);
let range = X86ToolingRange::new(
loc,
X86ToolingLocation::new(line_num + 1, line.len() + 1),
);
let name = self.extract_name_from_line(trimmed);
let m = X86ASTMatch::new(kind, loc, file.to_path_buf())
.with_name(name)
.with_range(range);
matches.push(m);
}
}
}
matches
}
fn extract_name_from_line(&self, line: &str) -> String {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") {
return String::new();
}
let patterns = [
("void ", "("),
("int ", "("),
("float ", "("),
("double ", "("),
("char ", "("),
("bool ", "("),
("auto ", "="),
("class ", "{"),
("struct ", "{"),
("enum ", "{"),
("namespace ", "{"),
("typedef ", ";"),
];
for (prefix, suffix) in &patterns {
if let Some(start) = trimmed.find(prefix) {
let after = &trimmed[start + prefix.len()..];
let end = after.find(|c: char| {
c == ' ' || c == '(' || c == '{' || c == ';' || c == '=' || c == ':'
});
let name = match end {
Some(e) => &after[..e],
None => after,
};
let name = name.trim();
if !name.is_empty() && name.chars().all(|c| c.is_alphanumeric() || c == '_') {
return name.to_string();
}
}
}
String::new()
}
fn line_contains_decl_named(&self, line: &str, name: &str) -> bool {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") {
return false;
}
for kw in &[
"void",
"int",
"float",
"double",
"char",
"bool",
"auto",
"class",
"struct",
"enum",
"union",
"namespace",
"using",
"typedef",
"static",
"extern",
"const",
"virtual",
] {
if trimmed.starts_with(kw) || trimmed.contains(&format!(" {} ", kw)) {
if trimmed.contains(name) {
return true;
}
}
}
if trimmed.contains(&format!("{}(", name)) || trimmed.contains(&format!("{} (", name)) {
return true;
}
false
}
fn line_matches_name_pattern(&self, line: &str, _pattern: &str) -> bool {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") {
return false;
}
for kw in &[
"void",
"int",
"char",
"class",
"struct",
"enum",
"namespace",
] {
if trimmed.starts_with(kw) || trimmed.contains(&format!(" {} ", kw)) {
return true;
}
}
false
}
fn line_has_type_annotation(&self, line: &str) -> bool {
let trimmed = line.trim();
for kw in &[
"int", "float", "double", "char", "void", "bool", "auto", "long", "short", "unsigned",
"signed",
] {
if trimmed.starts_with(kw) || trimmed.contains(&format!(" {} ", kw)) {
return true;
}
}
false
}
pub fn get_matches(&self) -> &[X86ASTMatch] {
&self.matches
}
pub fn clear(&mut self) {
self.matchers.clear();
self.matches.clear();
self.source_cache.clear();
}
pub fn filter_by_kind(&self, kind: X86MatchedNodeKind) -> Vec<&X86ASTMatch> {
self.matches
.iter()
.filter(|m| m.node_kind == kind)
.collect()
}
pub fn filter_by_name(&self, name: &str) -> Vec<&X86ASTMatch> {
self.matches
.iter()
.filter(|m| m.name.as_deref() == Some(name))
.collect()
}
pub fn match_count(&self) -> usize {
self.matches.len()
}
pub fn count_by_kind(&self, kind: X86MatchedNodeKind) -> usize {
self.matches.iter().filter(|m| m.node_kind == kind).count()
}
}
impl Default for X86ASTMatchers {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86RefactoringOpKind {
Insert,
Remove,
Replace,
}
#[derive(Debug, Clone)]
pub struct X86RefactoringEdit {
pub op: X86RefactoringOpKind,
pub file_path: PathBuf,
pub range: X86ToolingRange,
pub new_text: String,
pub description: String,
}
impl X86RefactoringEdit {
pub fn insert(
file_path: PathBuf,
location: X86ToolingLocation,
text: String,
desc: String,
) -> Self {
Self {
op: X86RefactoringOpKind::Insert,
file_path,
range: X86ToolingRange::new(location, location),
new_text: text,
description: desc,
}
}
pub fn remove(file_path: PathBuf, range: X86ToolingRange, desc: String) -> Self {
Self {
op: X86RefactoringOpKind::Remove,
file_path,
range,
new_text: String::new(),
description: desc,
}
}
pub fn replace(
file_path: PathBuf,
range: X86ToolingRange,
new_text: String,
desc: String,
) -> Self {
Self {
op: X86RefactoringOpKind::Replace,
file_path,
range,
new_text,
description: desc,
}
}
pub fn to_fixit(&self) -> X86ToolingFixIt {
X86ToolingFixIt::new(self.range, self.new_text.clone(), self.description.clone())
}
pub fn to_replacement(&self) -> X86ToolingReplacement {
X86ToolingReplacement::new(
self.file_path.clone(),
self.range,
self.new_text.clone(),
self.description.clone(),
)
}
}
impl fmt::Display for X86RefactoringEdit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let op_str = match self.op {
X86RefactoringOpKind::Insert => "INSERT",
X86RefactoringOpKind::Remove => "REMOVE",
X86RefactoringOpKind::Replace => "REPLACE",
};
write!(
f,
"{} {} {} '{}'",
op_str,
self.file_path.display(),
self.range,
self.description
)
}
}
pub struct X86RefactoringTool {
pub edits: Vec<X86RefactoringEdit>,
source_cache: HashMap<PathBuf, String>,
modified_cache: HashMap<PathBuf, String>,
pub fixit_hints: Vec<X86ToolingFixIt>,
pub header_source_map: HashMap<PathBuf, PathBuf>,
pub verbose: bool,
pub dry_run: bool,
}
impl X86RefactoringTool {
pub fn new() -> Self {
Self {
edits: Vec::new(),
source_cache: HashMap::new(),
modified_cache: HashMap::new(),
fixit_hints: Vec::new(),
header_source_map: HashMap::new(),
verbose: false,
dry_run: false,
}
}
pub fn add_edit(&mut self, edit: X86RefactoringEdit) {
self.fixit_hints.push(edit.to_fixit());
self.edits.push(edit);
}
pub fn insert(&mut self, file: &Path, location: X86ToolingLocation, text: &str, desc: &str) {
let edit = X86RefactoringEdit::insert(
file.to_path_buf(),
location,
text.to_string(),
desc.to_string(),
);
self.add_edit(edit);
}
pub fn remove(&mut self, file: &Path, range: X86ToolingRange, desc: &str) {
let edit = X86RefactoringEdit::remove(file.to_path_buf(), range, desc.to_string());
self.add_edit(edit);
}
pub fn replace(&mut self, file: &Path, range: X86ToolingRange, new_text: &str, desc: &str) {
let edit = X86RefactoringEdit::replace(
file.to_path_buf(),
range,
new_text.to_string(),
desc.to_string(),
);
self.add_edit(edit);
}
fn load_file(&mut self, path: &Path) -> Result<String, String> {
if let Some(content) = self.modified_cache.get(path) {
return Ok(content.clone());
}
if let Some(content) = self.source_cache.get(path) {
return Ok(content.clone());
}
let content = fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
self.source_cache
.insert(path.to_path_buf(), content.clone());
Ok(content)
}
pub fn execute_all(&mut self) -> X86ToolingResult {
let mut result = X86ToolingResult::new();
let mut file_edits: HashMap<PathBuf, Vec<X86RefactoringEdit>> = HashMap::new();
for edit in &self.edits {
file_edits
.entry(edit.file_path.clone())
.or_default()
.push(edit.clone());
}
result.files_processed = file_edits.len();
for (file_path, edits) in &file_edits {
let edits_refs: Vec<&X86RefactoringEdit> = edits.iter().collect();
match self.apply_edits_to_file(file_path, &edits_refs) {
Ok(modified) => {
self.modified_cache.insert(file_path.clone(), modified);
result.files_modified += 1;
}
Err(e) => {
result.success = false;
result.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(0, 0),
range: None,
message: e,
severity: X86ToolingSeverity::Error,
file_path: file_path.clone(),
fixit_hints: Vec::new(),
});
}
}
}
result.replacements = self.edits.iter().map(|e| e.to_replacement()).collect();
result
}
fn apply_edits_to_file(
&mut self,
file_path: &Path,
edits: &[&X86RefactoringEdit],
) -> Result<String, String> {
let source = self.load_file(file_path)?;
let lines: Vec<&str> = source.lines().collect();
let mut result = String::new();
let mut sorted_edits: Vec<&&X86RefactoringEdit> = edits.iter().collect();
sorted_edits.sort_by_key(|e| (e.range.start.line, e.range.start.column));
let mut line_idx = 0;
while line_idx < lines.len() {
let current_line = line_idx + 1; let edits_on_line: Vec<_> = sorted_edits
.iter()
.filter(|e| e.range.start.line == current_line)
.collect();
if edits_on_line.is_empty() {
result.push_str(lines[line_idx]);
result.push('\n');
line_idx += 1;
} else {
let line = lines[line_idx];
let mut new_line = String::new();
let mut col = 0;
for edit in &edits_on_line {
let start_col = edit.range.start.column.saturating_sub(1).min(line.len());
let end_col = edit.range.end.column.saturating_sub(1).min(line.len());
if col < start_col {
new_line.push_str(&line[col..start_col]);
}
col = start_col;
match edit.op {
X86RefactoringOpKind::Insert => {
new_line.push_str(&edit.new_text);
}
X86RefactoringOpKind::Replace => {
new_line.push_str(&edit.new_text);
col = end_col;
}
X86RefactoringOpKind::Remove => {
col = end_col;
}
}
}
if col < line.len() {
new_line.push_str(&line[col..]);
}
result.push_str(&new_line);
result.push('\n');
line_idx += 1;
}
}
Ok(result)
}
pub fn save_all(&mut self) -> Result<usize, String> {
if self.dry_run {
return Ok(self.modified_cache.len());
}
let mut count = 0;
for (path, content) in &self.modified_cache {
fs::write(path, content)
.map_err(|e| format!("Failed to write {}: {}", path.display(), e))?;
count += 1;
}
Ok(count)
}
pub fn coordinate_header_source(&mut self, header: &Path, source: &Path) -> Result<(), String> {
self.header_source_map
.insert(source.to_path_buf(), header.to_path_buf());
self.header_source_map
.insert(header.to_path_buf(), source.to_path_buf());
Ok(())
}
pub fn get_modified(&self, path: &Path) -> Option<&String> {
self.modified_cache.get(path)
}
pub fn get_original(&self, path: &Path) -> Option<&String> {
self.source_cache.get(path)
}
pub fn has_modifications(&self, path: &Path) -> bool {
self.modified_cache.contains_key(path)
}
pub fn generate_fixits(&self) -> Vec<X86ToolingFixIt> {
self.fixit_hints.clone()
}
pub fn edit_count(&self) -> usize {
self.edits.len()
}
pub fn clear(&mut self) {
self.edits.clear();
self.fixit_hints.clear();
self.modified_cache.clear();
}
}
impl Default for X86RefactoringTool {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86SymbolRefKind {
Definition,
Declaration,
Reference,
Call,
Override,
BaseClass,
DerivedClass,
FriendDecl,
TemplateSpecialization,
}
impl fmt::Display for X86SymbolRefKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Definition => write!(f, "definition"),
Self::Declaration => write!(f, "declaration"),
Self::Reference => write!(f, "reference"),
Self::Call => write!(f, "call"),
Self::Override => write!(f, "override"),
Self::BaseClass => write!(f, "base_class"),
Self::DerivedClass => write!(f, "derived_class"),
Self::FriendDecl => write!(f, "friend_decl"),
Self::TemplateSpecialization => write!(f, "template_specialization"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86SymbolRef {
pub symbol_name: String,
pub ref_kind: X86SymbolRefKind,
pub file_path: PathBuf,
pub location: X86ToolingLocation,
pub range: Option<X86ToolingRange>,
pub context: String,
}
impl X86SymbolRef {
pub fn new(
symbol_name: String,
ref_kind: X86SymbolRefKind,
file_path: PathBuf,
location: X86ToolingLocation,
) -> Self {
Self {
symbol_name,
ref_kind,
file_path,
location,
range: None,
context: String::new(),
}
}
}
impl fmt::Display for X86SymbolRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}: {} '{}' at {}:{}",
self.ref_kind,
self.file_path.display(),
self.symbol_name,
self.location.line,
self.location.column
)
}
}
pub struct X86ToolingFindSymbol {
source_cache: HashMap<PathBuf, String>,
pub references: Vec<X86SymbolRef>,
pub declarations: Vec<X86SymbolRef>,
pub overrides: Vec<X86SymbolRef>,
pub base_classes: Vec<X86SymbolRef>,
pub derived_classes: Vec<X86SymbolRef>,
pub search_symbol: Option<String>,
pub verbose: bool,
}
impl X86ToolingFindSymbol {
pub fn new() -> Self {
Self {
source_cache: HashMap::new(),
references: Vec::new(),
declarations: Vec::new(),
overrides: Vec::new(),
base_classes: Vec::new(),
derived_classes: Vec::new(),
search_symbol: None,
verbose: false,
}
}
pub fn set_symbol(&mut self, symbol: &str) {
self.search_symbol = Some(symbol.to_string());
}
pub fn run_on_file(&mut self, file: &Path) -> X86ToolingResult {
let mut result = X86ToolingResult::new();
result.files_processed = 1;
let source = match fs::read_to_string(file) {
Ok(s) => s,
Err(e) => {
result.success = false;
result.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(0, 0),
range: None,
message: format!("Failed to read {}: {}", file.display(), e),
severity: X86ToolingSeverity::Error,
file_path: file.to_path_buf(),
fixit_hints: Vec::new(),
});
return result;
}
};
self.source_cache.insert(file.to_path_buf(), source.clone());
if let Some(ref symbol) = self.search_symbol.clone() {
let refs = self.find_references_in_source(&source, symbol, file);
self.references.extend(refs);
let decls = self.find_declarations_in_source(&source, symbol, file);
self.declarations.extend(decls);
let overs = self.find_overrides_in_source(&source, symbol, file);
self.overrides.extend(overs);
let (bases, derived) = self.find_class_hierarchy_in_source(&source, symbol, file);
self.base_classes.extend(bases);
self.derived_classes.extend(derived);
}
result.success = true;
result
}
pub fn find_references_in_source(
&self,
source: &str,
symbol: &str,
file: &Path,
) -> Vec<X86SymbolRef> {
let mut refs = Vec::new();
for (line_num, line) in source.lines().enumerate() {
if line.contains(symbol) {
for (idx, _) in line.match_indices(symbol) {
let before = if idx > 0 {
line.as_bytes()[idx - 1] as char
} else {
' '
};
let after = if idx + symbol.len() < line.len() {
line.as_bytes()[idx + symbol.len()] as char
} else {
' '
};
if before.is_alphanumeric()
|| before == '_'
|| after.is_alphanumeric()
|| after == '_'
{
continue;
}
let loc = X86ToolingLocation::new(line_num + 1, idx + 1);
let range = X86ToolingRange::new(
loc,
X86ToolingLocation::new(line_num + 1, idx + symbol.len() + 1),
);
let ref_kind = self.classify_reference_line(line, symbol);
let mut r =
X86SymbolRef::new(symbol.to_string(), ref_kind, file.to_path_buf(), loc);
r.range = Some(range);
r.context = line.to_string();
refs.push(r);
}
}
}
refs
}
pub fn find_declarations_in_source(
&self,
source: &str,
symbol: &str,
file: &Path,
) -> Vec<X86SymbolRef> {
let mut decls = Vec::new();
for (line_num, line) in source.lines().enumerate() {
let trimmed = line.trim();
if self.is_declaration_line(trimmed, symbol) {
let loc = X86ToolingLocation::new(line_num + 1, 1);
let ref_kind = if trimmed.contains('{') {
X86SymbolRefKind::Definition
} else {
X86SymbolRefKind::Declaration
};
let mut d =
X86SymbolRef::new(symbol.to_string(), ref_kind, file.to_path_buf(), loc);
if let Some(idx) = trimmed.find(symbol) {
let range = X86ToolingRange::new(
X86ToolingLocation::new(line_num + 1, idx + 1),
X86ToolingLocation::new(line_num + 1, idx + symbol.len() + 1),
);
d.range = Some(range);
}
d.context = trimmed.to_string();
decls.push(d);
}
}
decls
}
fn is_declaration_line(&self, line: &str, symbol: &str) -> bool {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") {
return false;
}
for kw in &[
"void",
"int",
"float",
"double",
"char",
"bool",
"auto",
"class",
"struct",
"enum",
"union",
"namespace",
"using",
"typedef",
"static",
"extern",
"virtual",
"inline",
"constexpr",
] {
if trimmed.starts_with(kw) && trimmed.contains(symbol) {
return true;
}
}
if trimmed.contains(&format!("{}(", symbol)) || trimmed.contains(&format!("{} (", symbol)) {
return true;
}
if trimmed.contains("override") && trimmed.contains(symbol) {
return true;
}
false
}
pub fn find_overrides_in_source(
&self,
source: &str,
symbol: &str,
file: &Path,
) -> Vec<X86SymbolRef> {
let mut overs = Vec::new();
for (line_num, line) in source.lines().enumerate() {
let trimmed = line.trim();
if (trimmed.contains("override") || trimmed.contains("virtual"))
&& trimmed.contains(symbol)
{
let loc = X86ToolingLocation::new(line_num + 1, 1);
let mut r = X86SymbolRef::new(
symbol.to_string(),
X86SymbolRefKind::Override,
file.to_path_buf(),
loc,
);
r.context = trimmed.to_string();
overs.push(r);
}
}
overs
}
pub fn find_class_hierarchy_in_source(
&self,
source: &str,
symbol: &str,
file: &Path,
) -> (Vec<X86SymbolRef>, Vec<X86SymbolRef>) {
let mut bases = Vec::new();
let mut derived = Vec::new();
for (line_num, line) in source.lines().enumerate() {
let trimmed = line.trim();
if trimmed.contains("class") && trimmed.contains(':') && trimmed.contains(symbol) {
let loc = X86ToolingLocation::new(line_num + 1, 1);
let r = X86SymbolRef::new(
symbol.to_string(),
X86SymbolRefKind::BaseClass,
file.to_path_buf(),
loc,
);
bases.push(r);
}
if trimmed.contains(symbol) && trimmed.contains(": public ") {
let after_colon = trimmed.split(':').nth(1).unwrap_or("");
if !after_colon.trim().is_empty() {
let base_name = after_colon
.trim()
.split(|c: char| c.is_whitespace() || c == '{')
.next()
.unwrap_or("");
if !base_name.is_empty() && base_name != symbol {
let loc = X86ToolingLocation::new(line_num + 1, 1);
let r = X86SymbolRef::new(
base_name.to_string(),
X86SymbolRefKind::DerivedClass,
file.to_path_buf(),
loc,
);
derived.push(r);
}
}
}
}
(bases, derived)
}
fn classify_reference_line(&self, line: &str, symbol: &str) -> X86SymbolRefKind {
let trimmed = line.trim();
if trimmed.contains(&format!("{}(", symbol)) || trimmed.contains(&format!("{} (", symbol)) {
return X86SymbolRefKind::Call;
}
for kw in &["void", "int", "float", "double", "class", "struct"] {
if trimmed.starts_with(kw) && trimmed.contains(symbol) {
if trimmed.contains('{') || trimmed.ends_with(';') {
return X86SymbolRefKind::Definition;
}
return X86SymbolRefKind::Declaration;
}
}
if trimmed.contains("override") {
return X86SymbolRefKind::Override;
}
if trimmed.contains("friend") {
return X86SymbolRefKind::FriendDecl;
}
if trimmed.contains("template") && trimmed.contains('<') {
return X86SymbolRefKind::TemplateSpecialization;
}
X86SymbolRefKind::Reference
}
pub fn get_references(&self) -> &[X86SymbolRef] {
&self.references
}
pub fn get_declarations(&self) -> &[X86SymbolRef] {
&self.declarations
}
pub fn get_overrides(&self) -> &[X86SymbolRef] {
&self.overrides
}
pub fn get_base_classes(&self) -> &[X86SymbolRef] {
&self.base_classes
}
pub fn get_derived_classes(&self) -> &[X86SymbolRef] {
&self.derived_classes
}
pub fn reference_count(&self) -> usize {
self.references.len()
}
pub fn clear(&mut self) {
self.references.clear();
self.declarations.clear();
self.overrides.clear();
self.base_classes.clear();
self.derived_classes.clear();
self.source_cache.clear();
self.search_symbol = None;
}
}
impl Default for X86ToolingFindSymbol {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86ComplexityMetrics {
pub name: String,
pub line_count: usize,
pub cyclomatic_complexity: usize,
pub cognitive_complexity: usize,
pub halstead_volume: f64,
pub halstead_difficulty: f64,
pub halstead_effort: f64,
pub nesting_depth: usize,
pub parameter_count: usize,
pub statement_count: usize,
}
impl X86ComplexityMetrics {
pub fn new(name: String) -> Self {
Self {
name,
line_count: 0,
cyclomatic_complexity: 1, cognitive_complexity: 0,
halstead_volume: 0.0,
halstead_difficulty: 0.0,
halstead_effort: 0.0,
nesting_depth: 0,
parameter_count: 0,
statement_count: 0,
}
}
pub fn compute_cyclomatic(&mut self, lines: &[&str]) {
self.cyclomatic_complexity = 1; self.line_count = lines.len();
for line in lines {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") {
continue;
}
if trimmed.contains("if ") || trimmed.contains("if(") {
self.cyclomatic_complexity += 1;
}
if trimmed.contains("else if") || trimmed.contains("else if(") {
self.cyclomatic_complexity += 1;
}
if trimmed.contains("for ") || trimmed.contains("for(") {
self.cyclomatic_complexity += 1;
}
if trimmed.contains("while ") || trimmed.contains("while(") {
self.cyclomatic_complexity += 1;
}
if trimmed.contains("case ") {
self.cyclomatic_complexity += 1;
}
if trimmed.contains("&&") || trimmed.contains("||") {
self.cyclomatic_complexity += 1;
}
if trimmed.contains("catch ") || trimmed.contains("catch(") {
self.cyclomatic_complexity += 1;
}
if trimmed.contains("? ") || trimmed.contains("?:") {
self.cyclomatic_complexity += 1;
}
if trimmed.contains("break") || trimmed.contains("continue") {
self.statement_count += 1;
}
self.statement_count += 1;
}
}
pub fn compute_cognitive(&mut self, lines: &[&str]) {
self.cognitive_complexity = 0;
let mut nesting: usize = 0;
for line in lines {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") {
continue;
}
if trimmed.ends_with('{') {
nesting += 1;
}
if trimmed.starts_with('}') {
nesting = nesting.saturating_sub(1usize);
}
if trimmed.contains("if ") || trimmed.contains("if(") {
self.cognitive_complexity += 1 + nesting;
}
if trimmed.contains("else if") || trimmed.contains("else if(") {
self.cognitive_complexity += 1 + nesting;
}
if trimmed.contains("for ") || trimmed.contains("for(") {
self.cognitive_complexity += 1 + nesting;
}
if trimmed.contains("while ") || trimmed.contains("while(") {
self.cognitive_complexity += 1 + nesting;
}
if trimmed.contains("catch ") || trimmed.contains("catch(") {
self.cognitive_complexity += 1 + nesting;
}
if trimmed.contains("switch ") || trimmed.contains("switch(") {
self.cognitive_complexity += 1 + nesting;
}
if trimmed.contains("goto ") {
self.cognitive_complexity += 1;
}
if trimmed.contains("&&") {
self.cognitive_complexity += 1;
}
if trimmed.contains("||") {
self.cognitive_complexity += 1;
}
if trimmed.contains(&format!("{}(", self.name)) {
self.cognitive_complexity += 1;
}
}
self.nesting_depth = nesting;
}
pub fn compute_halstead(&mut self, lines: &[&str]) {
let mut operators = BTreeSet::new();
let mut operands = BTreeSet::new();
let mut total_operators: usize = 0;
let mut total_operands: usize = 0;
let operator_tokens = [
"+", "-", "*", "/", "%", "=", "==", "!=", "<", ">", "<=", ">=", "&&", "||", "!", "&",
"|", "^", "~", "<<", ">>", "++", "--", "+=", "-=", "*=", "/=", "%=", "->", ".", "::",
"?", ":", "(", ")", "{", "}", "[", "]", ";", ",",
];
for line in lines {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") {
continue;
}
for op in &operator_tokens {
let count = trimmed.matches(op).count();
if count > 0 {
operators.insert(op.to_string());
total_operators += count;
}
}
let words: Vec<&str> = trimmed
.split(|c: char| !c.is_alphanumeric() && c != '_')
.filter(|s| !s.is_empty())
.collect();
for w in words {
if !operator_tokens.contains(&w)
&& ![
"if",
"else",
"for",
"while",
"do",
"switch",
"case",
"break",
"continue",
"return",
"goto",
"class",
"struct",
"enum",
"void",
"int",
"float",
"double",
"char",
"bool",
"auto",
"const",
"static",
"virtual",
"override",
"public",
"private",
"protected",
"namespace",
"using",
"typedef",
"template",
"typename",
"new",
"delete",
"this",
"nullptr",
"true",
"false",
"sizeof",
"throw",
"catch",
"try",
"include",
"define",
]
.contains(&w)
{
operands.insert(w.to_string());
total_operands += 1;
}
}
}
let n1 = operators.len();
let n2 = operands.len();
let N1 = total_operators;
let N2 = total_operands;
let vocabulary = n1 + n2;
let length = N1 + N2;
if vocabulary == 0 || length == 0 {
return;
}
let volume = (length as f64) * (vocabulary as f64).log2();
let difficulty = if n2 == 0 {
0.0
} else {
(n1 as f64 / 2.0) * (N2 as f64 / n2 as f64)
};
let effort = volume * difficulty;
self.halstead_volume = volume;
self.halstead_difficulty = difficulty;
self.halstead_effort = effort;
}
}
impl fmt::Display for X86ComplexityMetrics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Complexity Metrics for '{}':", self.name)?;
writeln!(f, " Lines: {}", self.line_count)?;
writeln!(f, " Statements: {}", self.statement_count)?;
writeln!(f, " Parameters: {}", self.parameter_count)?;
writeln!(f, " Nesting Depth: {}", self.nesting_depth)?;
writeln!(
f,
" Cyclomatic Complexity: {}",
self.cyclomatic_complexity
)?;
writeln!(f, " Cognitive Complexity: {}", self.cognitive_complexity)?;
writeln!(f, " Halstead Volume: {:.2}", self.halstead_volume)?;
writeln!(
f,
" Halstead Difficulty: {:.2}",
self.halstead_difficulty
)?;
writeln!(f, " Halstead Effort: {:.2}", self.halstead_effort)
}
}
#[derive(Debug, Clone)]
pub struct X86DeadCodeInfo {
pub name: String,
pub kind: X86DeadCodeKind,
pub file_path: PathBuf,
pub location: X86ToolingLocation,
pub reason: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86DeadCodeKind {
UnusedFunction,
UnusedVariable,
UnusedParameter,
UnreachableCode,
DeadAssignment,
}
impl fmt::Display for X86DeadCodeKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnusedFunction => write!(f, "unused-function"),
Self::UnusedVariable => write!(f, "unused-variable"),
Self::UnusedParameter => write!(f, "unused-parameter"),
Self::UnreachableCode => write!(f, "unreachable-code"),
Self::DeadAssignment => write!(f, "dead-assignment"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86DuplicationInfo {
pub block_a_location: X86ToolingLocation,
pub block_a_lines: (usize, usize),
pub block_b_location: X86ToolingLocation,
pub block_b_lines: (usize, usize),
pub similarity: f64,
pub line_count: usize,
pub file_path: PathBuf,
}
pub struct X86ToolingCodeQuality {
pub complexity_metrics: Vec<X86ComplexityMetrics>,
pub dead_code: Vec<X86DeadCodeInfo>,
pub duplications: Vec<X86DuplicationInfo>,
source_cache: HashMap<PathBuf, String>,
pub verbose: bool,
pub duplication_threshold: f64,
}
impl X86ToolingCodeQuality {
pub fn new() -> Self {
Self {
complexity_metrics: Vec::new(),
dead_code: Vec::new(),
duplications: Vec::new(),
source_cache: HashMap::new(),
verbose: false,
duplication_threshold: 0.75,
}
}
pub fn analyze_file(&mut self, file: &Path) -> X86ToolingResult {
let mut result = X86ToolingResult::new();
result.files_processed = 1;
let source = match fs::read_to_string(file) {
Ok(s) => s,
Err(e) => {
result.success = false;
result.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(0, 0),
range: None,
message: format!("Failed to read {}: {}", file.display(), e),
severity: X86ToolingSeverity::Error,
file_path: file.to_path_buf(),
fixit_hints: Vec::new(),
});
return result;
}
};
self.source_cache.insert(file.to_path_buf(), source.clone());
let func_metrics = self.analyze_complexity(&source, file);
self.complexity_metrics.extend(func_metrics);
let dead = self.detect_dead_code(&source, file);
self.dead_code.extend(dead);
let dups = self.detect_duplication(&source, file);
self.duplications.extend(dups);
for dc in &self.dead_code {
if dc.file_path == file {
result.diagnostics.push(X86ToolingDiagnostic {
location: dc.location,
range: None,
message: format!("Dead code detected: {} ({})", dc.name, dc.reason),
severity: X86ToolingSeverity::Warning,
file_path: file.to_path_buf(),
fixit_hints: Vec::new(),
});
}
}
for cm in &self.complexity_metrics {
if cm.cyclomatic_complexity > 10 {
result.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(1, 1),
range: None,
message: format!(
"High cyclomatic complexity ({}) in '{}'",
cm.cyclomatic_complexity, cm.name
),
severity: X86ToolingSeverity::Warning,
file_path: file.to_path_buf(),
fixit_hints: Vec::new(),
});
}
if cm.cognitive_complexity > 15 {
result.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(1, 1),
range: None,
message: format!(
"High cognitive complexity ({}) in '{}'",
cm.cognitive_complexity, cm.name
),
severity: X86ToolingSeverity::Warning,
file_path: file.to_path_buf(),
fixit_hints: Vec::new(),
});
}
}
for dup in &self.duplications {
if dup.file_path == file {
result.diagnostics.push(X86ToolingDiagnostic {
location: dup.block_a_location,
range: None,
message: format!(
"Code duplication detected: {} lines, {:.0}% similar",
dup.line_count,
dup.similarity * 100.0
),
severity: X86ToolingSeverity::Warning,
file_path: file.to_path_buf(),
fixit_hints: Vec::new(),
});
}
}
result.files_modified = 0;
result.success = true;
result
}
fn analyze_complexity(&self, source: &str, _file: &Path) -> Vec<X86ComplexityMetrics> {
let mut metrics = Vec::new();
let lines: Vec<&str> = source.lines().collect();
let mut func_start: Option<usize> = None;
let mut func_name = String::new();
for (i, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") {
continue;
}
if func_start.is_none() && (trimmed.contains('(') || trimmed.contains(") {")) {
let is_func = trimmed.starts_with("void ")
|| trimmed.starts_with("int ")
|| trimmed.starts_with("float ")
|| trimmed.starts_with("double ")
|| trimmed.starts_with("char ")
|| trimmed.starts_with("bool ")
|| trimmed.starts_with("auto ")
|| trimmed.starts_with("static ")
|| trimmed.starts_with("inline ")
|| trimmed.starts_with("virtual ")
|| trimmed.starts_with("constexpr ")
|| trimmed.starts_with("unsigned ")
|| trimmed.starts_with("signed ")
|| trimmed.starts_with("long ")
|| trimmed.starts_with("short ")
|| (trimmed.contains("::") && trimmed.contains('('));
if is_func {
func_start = Some(i);
func_name = self.extract_function_name(trimmed);
}
}
if let Some(start) = func_start {
if trimmed.starts_with('}') || trimmed == "}" {
let func_lines: Vec<&str> = lines[start..=i].to_vec();
let mut cm = X86ComplexityMetrics::new(func_name.clone());
cm.parameter_count = self.count_parameters(&func_lines[0]);
cm.compute_cyclomatic(&func_lines);
cm.compute_cognitive(&func_lines);
cm.compute_halstead(&func_lines);
metrics.push(cm);
func_start = None;
func_name = String::new();
}
}
}
metrics
}
fn extract_function_name(&self, line: &str) -> String {
let trimmed = line.trim();
if let Some(idx) = trimmed.find('(') {
let before_paren = &trimmed[..idx];
let parts: Vec<&str> = before_paren.split_whitespace().collect();
if let Some(name) = parts.last() {
let clean: String = name
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_' || *c == ':')
.collect();
if !clean.is_empty()
&& !["if", "for", "while", "switch", "sizeof", "return", "catch"]
.contains(&clean.as_str())
{
return clean;
}
}
}
String::new()
}
fn count_parameters(&self, line: &str) -> usize {
if let Some(start) = line.find('(') {
if let Some(end) = line.rfind(')') {
let params = &line[start + 1..end];
if params.trim().is_empty() || params.trim() == "void" {
return 0;
}
let mut count: usize = 1;
let mut depth: usize = 0;
for ch in params.chars() {
if ch == '<' {
depth += 1;
} else if ch == '>' {
depth = depth.saturating_sub(1usize);
} else if ch == ',' && depth == 0 {
count += 1;
}
}
return count;
}
}
0
}
fn detect_dead_code(&self, source: &str, file: &Path) -> Vec<X86DeadCodeInfo> {
let mut dead = Vec::new();
let lines: Vec<&str> = source.lines().collect();
let mut defined: HashMap<String, usize> = HashMap::new();
let mut referenced: HashSet<String> = HashSet::new();
for (i, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") {
continue;
}
for kw in &["void", "int", "float", "double", "char", "bool"] {
if trimmed.starts_with(kw) {
if let Some(name) = self.extract_name_from_decl(trimmed) {
defined.entry(name.clone()).or_insert(i);
if trimmed.contains('{')
|| (trimmed.ends_with(';') && !trimmed.contains('('))
{
}
}
}
}
let words: Vec<&str> = trimmed
.split(|c: char| !c.is_alphanumeric() && c != '_')
.filter(|s| !s.is_empty())
.collect();
for w in words {
if ![
"if",
"else",
"for",
"while",
"do",
"switch",
"case",
"break",
"continue",
"return",
"void",
"int",
"float",
"double",
"char",
"bool",
"auto",
"const",
"static",
"sizeof",
"new",
"delete",
"this",
"nullptr",
"true",
"false",
"class",
"struct",
"enum",
"namespace",
"using",
"typedef",
"include",
"define",
]
.contains(&w)
{
let s = w.to_string();
if !s.is_empty()
&& s.chars()
.next()
.map_or(false, |c| c.is_alphabetic() || c == '_')
{
referenced.insert(s);
}
}
}
}
for (name, line) in &defined {
if !referenced.contains(name) && name != "main" {
dead.push(X86DeadCodeInfo {
name: name.clone(),
kind: X86DeadCodeKind::UnusedFunction,
file_path: file.to_path_buf(),
location: X86ToolingLocation::new(*line + 1, 1),
reason: "Function is defined but never called".to_string(),
});
}
}
let mut after_terminator = false;
for (i, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") {
continue;
}
if after_terminator && !trimmed.is_empty() && !trimmed.starts_with('}') {
dead.push(X86DeadCodeInfo {
name: format!("unreachable_line_{}", i + 1),
kind: X86DeadCodeKind::UnreachableCode,
file_path: file.to_path_buf(),
location: X86ToolingLocation::new(i + 1, 1),
reason: "Code after return/break/continue without intervening label"
.to_string(),
});
after_terminator = false; }
if trimmed == "return"
|| trimmed.starts_with("return ")
|| trimmed.starts_with("return;")
|| trimmed == "break;"
|| trimmed == "continue;"
{
after_terminator = true;
}
if trimmed.starts_with('}') {
after_terminator = false;
}
}
for (i, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.contains('(') && trimmed.contains(')') {
if let Some(name) = self.extract_name_from_decl(trimmed) {
let params_start = trimmed.find('(').unwrap();
let params_end = trimmed.rfind(')').unwrap();
if params_end > params_start + 1 {
let params = &trimmed[params_start + 1..params_end];
for param in params.split(',') {
let param = param.trim();
if param.is_empty() || param == "void" {
continue;
}
let parts: Vec<&str> = param.split_whitespace().collect();
if let Some(param_name) = parts.last() {
let clean: String = param_name
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
if !clean.is_empty() {
let mut used = false;
for j in i + 1..lines.len() {
let l = lines[j].trim();
if l.starts_with('}') {
break;
}
if l.contains(&clean) {
used = true;
break;
}
}
if !used && clean != "void" {
dead.push(X86DeadCodeInfo {
name: clean.clone(),
kind: X86DeadCodeKind::UnusedParameter,
file_path: file.to_path_buf(),
location: X86ToolingLocation::new(i + 1, 1),
reason: format!(
"Parameter '{}' is not used in function '{}'",
clean, name
),
});
}
}
}
}
}
}
}
}
dead
}
fn extract_name_from_decl(&self, line: &str) -> Option<String> {
let trimmed = line.trim();
if let Some(idx) = trimmed.find('(') {
let before = &trimmed[..idx];
let parts: Vec<&str> = before.split_whitespace().collect();
if let Some(name) = parts.last() {
let clean: String = name
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_' || *c == ':')
.collect();
if !clean.is_empty()
&& !["if", "for", "while", "switch", "sizeof", "return", "catch"]
.contains(&clean.as_str())
{
return Some(clean);
}
}
}
if trimmed.contains('=') || trimmed.ends_with(';') {
let parts: Vec<&str> = trimmed
.split(|c: char| c.is_whitespace() || c == '=' || c == ';')
.filter(|s| !s.is_empty())
.collect();
if parts.len() >= 2 {
for kw in &["int", "float", "double", "char", "bool", "auto"] {
if parts[0] == *kw {
let clean: String = parts[1]
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
if !clean.is_empty() {
return Some(clean);
}
}
}
}
}
None
}
fn detect_duplication(&self, source: &str, file: &Path) -> Vec<X86DuplicationInfo> {
let mut dups = Vec::new();
let lines: Vec<&str> = source
.lines()
.filter(|l| {
let t = l.trim();
!t.is_empty() && !t.starts_with("//") && !t.starts_with("/*") && !t.starts_with('*')
})
.map(|l| l.trim())
.collect();
let min_lines = 5; if lines.len() < min_lines * 2 {
return dups;
}
for i in 0..lines.len().saturating_sub(min_lines) {
for j in (i + min_lines)..lines.len().saturating_sub(min_lines) {
let mut match_count = 0;
let mut max_match = 0;
let mut total_checked = 0;
for k in 0..min_lines.min(lines.len() - j) {
if i + k < lines.len() {
total_checked += 1;
if lines[i + k] == lines[j + k] {
match_count += 1;
max_match += 1;
} else {
max_match = 0;
}
}
}
let similarity = if total_checked > 0 {
match_count as f64 / total_checked as f64
} else {
0.0
};
if similarity >= self.duplication_threshold && match_count >= min_lines {
dups.push(X86DuplicationInfo {
block_a_location: X86ToolingLocation::new(i + 1, 1),
block_a_lines: (i + 1, i + match_count),
block_b_location: X86ToolingLocation::new(j + 1, 1),
block_b_lines: (j + 1, j + match_count),
similarity,
line_count: match_count,
file_path: file.to_path_buf(),
});
}
}
}
let mut unique_dups: Vec<X86DuplicationInfo> = Vec::new();
for dup in &dups {
let is_duplicate = unique_dups.iter().any(|existing| {
(existing.block_a_lines.0 == dup.block_a_lines.0
&& existing.block_b_lines.0 == dup.block_b_lines.0)
|| (existing.block_a_lines.0 == dup.block_b_lines.0
&& existing.block_b_lines.0 == dup.block_a_lines.0)
});
if !is_duplicate {
unique_dups.push(dup.clone());
}
}
unique_dups
}
pub fn generate_report(&self, file: &Path) -> String {
let mut report = String::new();
report.push_str(&format!(
"=== Code Quality Report: {} ===\n\n",
file.display()
));
report.push_str("--- Complexity Metrics ---\n");
if self.complexity_metrics.is_empty() {
report.push_str(" No functions analyzed.\n");
} else {
for cm in &self.complexity_metrics {
report.push_str(&format!(
" {}: cyclomatic={}, cognitive={}, lines={}\n",
cm.name, cm.cyclomatic_complexity, cm.cognitive_complexity, cm.line_count
));
}
}
report.push_str("\n--- Dead Code ---\n");
let file_dead: Vec<_> = self
.dead_code
.iter()
.filter(|d| d.file_path == file)
.collect();
if file_dead.is_empty() {
report.push_str(" No dead code detected.\n");
} else {
for dc in &file_dead {
report.push_str(&format!(
" [{}] {} at line {}: {}\n",
dc.kind, dc.name, dc.location.line, dc.reason
));
}
}
report.push_str("\n--- Code Duplication ---\n");
let file_dups: Vec<_> = self
.duplications
.iter()
.filter(|d| d.file_path == file)
.collect();
if file_dups.is_empty() {
report.push_str(" No duplications detected.\n");
} else {
for dup in &file_dups {
report.push_str(&format!(
" Lines {}-{} similar to lines {}-{}: {:.0}% match ({} lines)\n",
dup.block_a_lines.0,
dup.block_a_lines.1,
dup.block_b_lines.0,
dup.block_b_lines.1,
dup.similarity * 100.0,
dup.line_count
));
}
}
report
}
pub fn get_complexity_metrics(&self) -> &[X86ComplexityMetrics] {
&self.complexity_metrics
}
pub fn get_dead_code(&self) -> &[X86DeadCodeInfo] {
&self.dead_code
}
pub fn get_duplications(&self) -> &[X86DuplicationInfo] {
&self.duplications
}
pub fn clear(&mut self) {
self.complexity_metrics.clear();
self.dead_code.clear();
self.duplications.clear();
self.source_cache.clear();
}
}
impl Default for X86ToolingCodeQuality {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
fn temp_file(name: &str, content: &str) -> PathBuf {
let dir = std::env::temp_dir();
let path = dir.join(format!("x86_tooling_test_{}", name));
fs::write(&path, content).unwrap();
path
}
#[test]
fn test_tooling_full_new() {
let tf = X86ToolingFull::new();
assert!(tf.source_files.is_empty());
assert!(!tf.verbose);
assert!(tf.results.is_empty());
}
#[test]
fn test_tooling_full_with_working_dir() {
let tf = X86ToolingFull::new().with_working_dir(PathBuf::from("/tmp"));
assert_eq!(tf.working_dir, PathBuf::from("/tmp"));
}
#[test]
fn test_tooling_full_add_file() {
let mut tf = X86ToolingFull::new();
tf.add_file(PathBuf::from("test.c"));
assert_eq!(tf.source_files.len(), 1);
}
#[test]
fn test_tooling_full_with_verbose() {
let tf = X86ToolingFull::new().with_verbose(true);
assert!(tf.verbose);
assert!(tf.lib_tooling.verbose);
assert!(tf.ast_matchers.verbose);
assert!(tf.refactoring_tool.verbose);
assert!(tf.find_symbol.verbose);
assert!(tf.code_quality.verbose);
}
#[test]
fn test_tooling_full_generate_summary() {
let tf = X86ToolingFull::new();
let summary = tf.generate_summary();
assert_eq!(summary.total_files, 0);
assert_eq!(summary.total_operations, 0);
assert_eq!(summary.total_errors, 0);
}
#[test]
fn test_tooling_full_run_all_no_files() {
let mut tf = X86ToolingFull::new();
let results = tf.run_all();
assert!(!results.is_empty());
}
#[test]
fn test_tooling_full_with_compilation_db() {
let tf = X86ToolingFull::new().with_compilation_db(PathBuf::from("compile_commands.json"));
assert!(tf.compilation_db_path.is_some());
}
#[test]
fn test_tooling_full_default() {
let tf = X86ToolingFull::default();
assert!(tf.source_files.is_empty());
}
#[test]
fn test_tooling_full_summary_display() {
let summary = X86ToolingFullSummary {
total_files: 3,
total_operations: 10,
total_errors: 2,
total_warnings: 5,
total_replacements: 8,
files_modified: 2,
};
let disp = format!("{}", summary);
assert!(disp.contains("3"));
assert!(disp.contains("10"));
assert!(disp.contains("2"));
}
#[test]
fn test_location_new() {
let loc = X86ToolingLocation::new(5, 10);
assert_eq!(loc.line, 5);
assert_eq!(loc.column, 10);
}
#[test]
fn test_location_display() {
let loc = X86ToolingLocation::new(3, 7);
assert_eq!(format!("{}", loc), "3:7");
}
#[test]
fn test_range_contains() {
let start = X86ToolingLocation::new(1, 1);
let end = X86ToolingLocation::new(10, 20);
let range = X86ToolingRange::new(start, end);
assert!(range.contains(X86ToolingLocation::new(5, 5)));
assert!(range.contains(X86ToolingLocation::new(1, 1)));
assert!(range.contains(X86ToolingLocation::new(10, 20)));
assert!(!range.contains(X86ToolingLocation::new(11, 1)));
assert!(!range.contains(X86ToolingLocation::new(0, 5)));
}
#[test]
fn test_range_overlaps() {
let r1 = X86ToolingRange::new(
X86ToolingLocation::new(1, 1),
X86ToolingLocation::new(5, 10),
);
let r2 = X86ToolingRange::new(
X86ToolingLocation::new(3, 1),
X86ToolingLocation::new(7, 10),
);
let r3 = X86ToolingRange::new(
X86ToolingLocation::new(6, 1),
X86ToolingLocation::new(10, 10),
);
assert!(r1.overlaps(&r2));
assert!(r2.overlaps(&r3));
assert!(!r1.overlaps(&r3));
}
#[test]
fn test_range_display() {
let range = X86ToolingRange::new(
X86ToolingLocation::new(1, 1),
X86ToolingLocation::new(5, 10),
);
let s = format!("{}", range);
assert!(s.contains("1:1"));
assert!(s.contains("5:10"));
}
#[test]
fn test_replacement_new() {
let r = X86ToolingReplacement::new(
PathBuf::from("test.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 5)),
"hello".to_string(),
"test replacement".to_string(),
);
assert_eq!(r.file_path, PathBuf::from("test.c"));
assert_eq!(r.replacement_text, "hello");
}
#[test]
fn test_replacement_apply() {
let r = X86ToolingReplacement::new(
PathBuf::from("test.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 1)),
"X".to_string(),
"insert".to_string(),
);
let source = "abc\ndef\n";
let result = r.apply(source);
assert!(result.is_some());
assert!(result.unwrap().contains("X"));
}
#[test]
fn test_replacement_conflicts() {
let r1 = X86ToolingReplacement::new(
PathBuf::from("test.c"),
X86ToolingRange::new(
X86ToolingLocation::new(1, 1),
X86ToolingLocation::new(1, 10),
),
"".to_string(),
"r1".to_string(),
);
let r2 = X86ToolingReplacement::new(
PathBuf::from("test.c"),
X86ToolingRange::new(
X86ToolingLocation::new(1, 5),
X86ToolingLocation::new(1, 15),
),
"".to_string(),
"r2".to_string(),
);
assert!(r1.conflicts_with(&r2));
}
#[test]
fn test_replacement_no_conflict_different_file() {
let r1 = X86ToolingReplacement::new(
PathBuf::from("a.c"),
X86ToolingRange::new(
X86ToolingLocation::new(1, 1),
X86ToolingLocation::new(1, 10),
),
"".to_string(),
"r1".to_string(),
);
let r2 = X86ToolingReplacement::new(
PathBuf::from("b.c"),
X86ToolingRange::new(
X86ToolingLocation::new(1, 1),
X86ToolingLocation::new(1, 10),
),
"".to_string(),
"r2".to_string(),
);
assert!(!r1.conflicts_with(&r2));
}
#[test]
fn test_replacement_display() {
let r = X86ToolingReplacement::new(
PathBuf::from("test.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 5)),
"fix".to_string(),
"test".to_string(),
);
let s = format!("{}", r);
assert!(s.contains("test.c"));
assert!(s.contains("fix"));
}
#[test]
fn test_tooling_result_new() {
let r = X86ToolingResult::new();
assert!(r.success);
assert_eq!(r.files_processed, 0);
assert_eq!(r.error_count(), 0);
}
#[test]
fn test_tooling_result_has_errors() {
let mut r = X86ToolingResult::new();
assert!(!r.has_errors());
r.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(1, 1),
range: None,
message: "error".into(),
severity: X86ToolingSeverity::Error,
file_path: PathBuf::from("test.c"),
fixit_hints: Vec::new(),
});
assert!(r.has_errors());
}
#[test]
fn test_tooling_severity_display() {
assert_eq!(format!("{}", X86ToolingSeverity::Warning), "warning");
assert_eq!(format!("{}", X86ToolingSeverity::Error), "error");
assert_eq!(format!("{}", X86ToolingSeverity::Fatal), "fatal");
}
#[test]
fn test_fixit_new() {
let fixit = X86ToolingFixIt::new(
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 5)),
"replacement".to_string(),
"fix it".to_string(),
);
assert_eq!(fixit.replacement_text, "replacement");
assert_eq!(fixit.description, "fix it");
}
#[test]
fn test_frontend_action_new() {
let action =
X86FrontendAction::new(X86FrontendActionKind::SyntaxOnly, PathBuf::from("test.c"));
assert_eq!(action.kind, X86FrontendActionKind::SyntaxOnly);
assert_eq!(action.source_file, PathBuf::from("test.c"));
}
#[test]
fn test_frontend_action_syntax_only() {
let action = X86FrontendAction::syntax_only(PathBuf::from("test.cpp"));
assert_eq!(action.kind, X86FrontendActionKind::SyntaxOnly);
}
#[test]
fn test_frontend_action_plugin() {
let action = X86FrontendAction::plugin_action("my-plugin", PathBuf::from("test.cpp"));
assert_eq!(action.kind, X86FrontendActionKind::PluginAction);
assert_eq!(action.name, "my-plugin");
}
#[test]
fn test_frontend_action_kind_display() {
assert_eq!(
format!("{}", X86FrontendActionKind::SyntaxOnly),
"syntax-only"
);
assert_eq!(format!("{}", X86FrontendActionKind::ASTBuild), "ast-build");
assert_eq!(format!("{}", X86FrontendActionKind::EmitFull), "emit-full");
}
#[test]
fn test_compilation_db_entry_new() {
let entry = X86CompilationDBEntry::new(PathBuf::from("/src"), PathBuf::from("/src/main.c"));
assert_eq!(entry.directory, PathBuf::from("/src"));
assert!(entry.arguments.is_empty());
}
#[test]
fn test_compilation_db_new() {
let db = X86CompilationDatabase::new();
assert!(db.is_empty());
assert_eq!(db.len(), 0);
}
#[test]
fn test_compilation_db_load_from_file() {
let path = temp_file(
"compile_commands.json",
r#"[
{"directory": "/src", "file": "main.c", "command": "gcc -c main.c"}
]"#,
);
let mut db = X86CompilationDatabase::new();
let result = db.load_from_file(&path);
assert!(result.is_ok());
assert!(db.loaded);
assert_eq!(db.len(), 1);
fs::remove_file(&path).ok();
}
#[test]
fn test_compilation_db_load_empty() {
let path = temp_file("empty_compile_commands.json", "[]");
let mut db = X86CompilationDatabase::new();
db.load_from_file(&path).ok();
assert!(db.is_empty());
fs::remove_file(&path).ok();
}
#[test]
fn test_compilation_db_lookup() {
let mut db = X86CompilationDatabase::new();
db.entries.push(X86CompilationDBEntry::new(
PathBuf::from("/src"),
PathBuf::from("main.c"),
));
let entry = db.lookup(Path::new("main.c"));
assert!(entry.is_some());
}
#[test]
fn test_compilation_db_lookup_not_found() {
let db = X86CompilationDatabase::new();
assert!(db.lookup(Path::new("nonexistent.c")).is_none());
}
#[test]
fn test_compilation_db_collect_include_paths() {
let mut db = X86CompilationDatabase::new();
let mut entry = X86CompilationDBEntry::new(PathBuf::from("/src"), PathBuf::from("main.c"));
entry.arguments = vec![
"gcc".to_string(),
"-I/usr/include".to_string(),
"-I".to_string(),
"/opt/include".to_string(),
];
db.entries.push(entry);
let paths = db.collect_include_paths();
assert!(!paths.is_empty());
}
#[test]
fn test_compilation_db_default() {
let db = X86CompilationDatabase::default();
assert!(db.is_empty());
}
#[test]
fn test_tool_executor_new() {
let exec = X86ToolExecutor::new(X86ToolExecutionMode::Standalone);
assert!(exec.actions.is_empty());
}
#[test]
fn test_tool_executor_with_db() {
let db = X86CompilationDatabase::new();
let exec = X86ToolExecutor::new(X86ToolExecutionMode::Standalone).with_db(db);
assert!(exec.compilation_db.is_empty());
}
#[test]
fn test_tool_executor_add_action() {
let mut exec = X86ToolExecutor::new(X86ToolExecutionMode::Standalone);
exec.add_action(X86FrontendAction::syntax_only(PathBuf::from("test.c")));
assert_eq!(exec.actions.len(), 1);
}
#[test]
fn test_tool_executor_execute_standalone() {
let mut exec = X86ToolExecutor::new(X86ToolExecutionMode::Standalone);
exec.add_action(X86FrontendAction::syntax_only(PathBuf::from("test.c")));
let results = exec.execute();
assert_eq!(results.results.len(), 1);
assert!(!results.results[0].success);
}
#[test]
fn test_tool_executor_execute_mapreduce() {
let mut exec = X86ToolExecutor::new(X86ToolExecutionMode::MapReduce);
exec.add_action(X86FrontendAction::new(
X86FrontendActionKind::ASTBuild,
PathBuf::from("nonexistent.c"),
));
let results = exec.execute();
assert!(!results.results.is_empty());
}
#[test]
fn test_tool_executor_execute_parallel() {
let mut exec = X86ToolExecutor::new(X86ToolExecutionMode::Parallel);
exec.add_action(X86FrontendAction::syntax_only(PathBuf::from(
"nonexistent.c",
)));
let results = exec.execute();
assert_eq!(results.results.len(), 1);
}
#[test]
fn test_tool_results_aggregate() {
let mut results = X86ToolResults::new(X86ToolExecutionMode::Standalone);
let mut r1 = X86ToolingResult::new();
r1.files_processed = 1;
r1.files_modified = 1;
let r2 = X86ToolingResult::new();
results.results = vec![r1, r2];
results.aggregate();
assert_eq!(results.aggregated.files_processed, 1);
assert_eq!(results.aggregated.files_modified, 1);
}
#[test]
fn test_tool_results_to_json() {
let mut results = X86ToolResults::new(X86ToolExecutionMode::Standalone);
results.aggregated.files_processed = 2;
results.aggregated.files_modified = 1;
let json = results.to_json();
assert!(json.contains("Standalone"));
assert!(json.contains("files_processed"));
}
#[test]
fn test_lib_tooling_new() {
let lt = X86LibTooling::new();
assert!(lt.frontend_actions.is_empty());
assert_eq!(lt.compilation_db.len(), 0);
}
#[test]
fn test_lib_tooling_add_frontend_action() {
let mut lt = X86LibTooling::new();
lt.add_frontend_action(X86FrontendAction::syntax_only(PathBuf::from("test.c")));
assert_eq!(lt.frontend_actions.len(), 1);
}
#[test]
fn test_lib_tooling_create_syntax_action() {
let lt = X86LibTooling::new();
let action = lt.create_syntax_action(Path::new("test.c"));
assert_eq!(action.kind, X86FrontendActionKind::SyntaxOnly);
}
#[test]
fn test_lib_tooling_create_plugin_action() {
let lt = X86LibTooling::new();
let action = lt.create_plugin_action("check-style", Path::new("test.cpp"));
assert_eq!(action.kind, X86FrontendActionKind::PluginAction);
assert_eq!(action.name, "check-style");
}
#[test]
fn test_lib_tooling_set_execution_mode() {
let mut lt = X86LibTooling::new();
lt.set_execution_mode(X86ToolExecutionMode::Parallel);
}
#[test]
fn test_lib_tooling_load_compilation_db() {
let path = temp_file(
"test_db.json",
r#"[
{"directory": "/src", "file": "main.c", "command": "gcc -c main.c"}
]"#,
);
let mut lt = X86LibTooling::new();
let result = lt.load_compilation_database(&path);
assert!(result.success);
fs::remove_file(&path).ok();
}
#[test]
fn test_lib_tooling_load_compilation_db_nonexistent() {
let mut lt = X86LibTooling::new();
let result = lt.load_compilation_database(Path::new("nonexistent.json"));
assert!(!result.success);
}
#[test]
fn test_lib_tooling_run_action_nonexistent() {
let mut lt = X86LibTooling::new();
let result = lt.run_action(X86FrontendAction::syntax_only(PathBuf::from(
"nonexistent.c",
)));
assert!(!result.success);
}
#[test]
fn test_lib_tooling_default() {
let lt = X86LibTooling::default();
assert!(lt.frontend_actions.is_empty());
}
#[test]
fn test_ast_matchers_new() {
let matchers = X86ASTMatchers::new();
assert!(matchers.matchers.is_empty());
assert_eq!(matchers.match_count(), 0);
}
#[test]
fn test_node_kind_display() {
assert_eq!(
format!("{}", X86MatchedNodeKind::FunctionDecl),
"functionDecl"
);
assert_eq!(format!("{}", X86MatchedNodeKind::IfStmt), "ifStmt");
assert_eq!(
format!("{}", X86MatchedNodeKind::IntegerLiteral),
"integerLiteral"
);
assert_eq!(
format!("{}", X86MatchedNodeKind::PointerType),
"pointerType"
);
}
#[test]
fn test_predicate_to_query_string() {
let p = X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::FunctionDecl);
assert_eq!(p.to_query_string(), "functionDecl");
let p = X86ASTMatcherPredicate::HasName("foo".into());
assert_eq!(p.to_query_string(), "hasName(\"foo\")");
let p = X86ASTMatcherPredicate::IsDefinition;
assert_eq!(p.to_query_string(), "isDefinition()");
let p = X86ASTMatcherPredicate::IsVirtual;
assert_eq!(p.to_query_string(), "isVirtual()");
let p = X86ASTMatcherPredicate::AllOf(vec![
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::FunctionDecl),
X86ASTMatcherPredicate::HasName("main".into()),
]);
let qs = p.to_query_string();
assert!(qs.contains("allOf"));
assert!(qs.contains("functionDecl"));
assert!(qs.contains("hasName"));
}
#[test]
fn test_matcher_construction_node() {
let m = X86ASTMatchers::function_decl();
assert!(matches!(
m,
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::FunctionDecl)
));
let m = X86ASTMatchers::var_decl();
assert!(matches!(
m,
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::VarDecl)
));
let m = X86ASTMatchers::cxx_record_decl();
assert!(matches!(
m,
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXRecordDecl)
));
}
#[test]
fn test_matcher_construction_narrowing() {
let m = X86ASTMatchers::has_name("main");
assert!(matches!(m, X86ASTMatcherPredicate::HasName(_)));
let m = X86ASTMatchers::is_definition();
assert!(matches!(m, X86ASTMatcherPredicate::IsDefinition));
let m = X86ASTMatchers::is_virtual();
assert!(matches!(m, X86ASTMatcherPredicate::IsVirtual));
let m = X86ASTMatchers::is_const();
assert!(matches!(m, X86ASTMatcherPredicate::IsConst));
let m = X86ASTMatchers::is_static();
assert!(matches!(m, X86ASTMatcherPredicate::IsStatic));
let m = X86ASTMatchers::is_constexpr();
assert!(matches!(m, X86ASTMatcherPredicate::IsConstexpr));
}
#[test]
fn test_matcher_construction_expr() {
let m = X86ASTMatchers::call_expr();
assert!(matches!(
m,
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CallExpr)
));
let m = X86ASTMatchers::integer_literal();
assert!(matches!(
m,
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::IntegerLiteral)
));
let m = X86ASTMatchers::string_literal();
assert!(matches!(
m,
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::StringLiteral)
));
let m = X86ASTMatchers::lambda_expr();
assert!(matches!(
m,
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::LambdaExpr)
));
}
#[test]
fn test_matcher_construction_type() {
let m = X86ASTMatchers::pointer_type();
assert!(matches!(
m,
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::PointerType)
));
let m = X86ASTMatchers::is_integer();
assert!(matches!(m, X86ASTMatcherPredicate::IsInteger));
let m = X86ASTMatchers::is_const_qualified();
assert!(matches!(m, X86ASTMatcherPredicate::IsConstQualified));
}
#[test]
fn test_matcher_construction_stmt() {
let m = X86ASTMatchers::if_stmt();
assert!(matches!(
m,
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::IfStmt)
));
let m = X86ASTMatchers::return_stmt();
assert!(matches!(
m,
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ReturnStmt)
));
let m = X86ASTMatchers::cxx_try_stmt();
assert!(matches!(
m,
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXTryStmt)
));
}
#[test]
fn test_matcher_construction_combinators() {
let m = X86ASTMatchers::all_of(vec![X86ASTMatchers::function_decl()]);
assert!(matches!(m, X86ASTMatcherPredicate::AllOf(_)));
let m = X86ASTMatchers::any_of(vec![X86ASTMatchers::var_decl()]);
assert!(matches!(m, X86ASTMatcherPredicate::AnyOf(_)));
let m = X86ASTMatchers::unless(X86ASTMatchers::anything());
assert!(matches!(m, X86ASTMatcherPredicate::Unless(_)));
let m = X86ASTMatchers::anything();
assert!(matches!(m, X86ASTMatcherPredicate::Anything));
}
#[test]
fn test_add_matcher() {
let mut matchers = X86ASTMatchers::new();
matchers.add_matcher(X86ASTMatchers::function_decl());
assert_eq!(matchers.matchers.len(), 1);
}
#[test]
fn test_match_in_source_has_name() {
let matchers = X86ASTMatchers::new();
let source = "void foo() { }\nint bar() { return 0; }\nvoid baz() { }";
let file = Path::new("test.c");
let predicate = X86ASTMatcherPredicate::HasName("foo".into());
let matches = matchers.match_in_source(source, &predicate, file);
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.name.as_deref() == Some("foo")));
}
#[test]
fn test_match_in_source_is_static() {
let matchers = X86ASTMatchers::new();
let source = "static int x = 5;\nint y = 10;\nstatic void foo() { }";
let file = Path::new("test.c");
let matches = matchers.match_in_source(source, &X86ASTMatcherPredicate::IsStatic, file);
assert!(!matches.is_empty());
}
#[test]
fn test_match_in_source_is_const() {
let matchers = X86ASTMatchers::new();
let source = "const int MAX = 100;\nint x = 5;";
let file = Path::new("test.c");
let matches = matchers.match_in_source(source, &X86ASTMatcherPredicate::IsConst, file);
assert!(!matches.is_empty());
}
#[test]
fn test_match_in_source_is_virtual() {
let matchers = X86ASTMatchers::new();
let source = "class Base { virtual void foo(); };\nclass Derived { void bar(); };";
let file = Path::new("test.cpp");
let matches = matchers.match_in_source(source, &X86ASTMatcherPredicate::IsVirtual, file);
assert!(!matches.is_empty());
}
#[test]
fn test_match_in_source_is_inline() {
let matchers = X86ASTMatchers::new();
let source = "inline int add(int a, int b) { return a + b; }";
let file = Path::new("test.c");
let matches = matchers.match_in_source(source, &X86ASTMatcherPredicate::IsInline, file);
assert!(!matches.is_empty());
}
#[test]
fn test_match_in_source_is_constexpr() {
let matchers = X86ASTMatchers::new();
let source = "constexpr int square(int x) { return x * x; }";
let file = Path::new("test.cpp");
let matches = matchers.match_in_source(source, &X86ASTMatcherPredicate::IsConstexpr, file);
assert!(!matches.is_empty());
}
#[test]
fn test_match_in_source_has_string_value() {
let matchers = X86ASTMatchers::new();
let source = r#"const char* msg = "hello";"#;
let file = Path::new("test.c");
let predicate = X86ASTMatcherPredicate::HasStringValue("hello".into());
let matches = matchers.match_in_source(source, &predicate, file);
assert!(!matches.is_empty());
}
#[test]
fn test_match_in_source_has_integer_value() {
let matchers = X86ASTMatchers::new();
let source = "int x = 42;";
let file = Path::new("test.c");
let predicate = X86ASTMatcherPredicate::HasIntegerValue(42);
let matches = matchers.match_in_source(source, &predicate, file);
assert!(!matches.is_empty());
}
#[test]
fn test_match_in_source_all_of() {
let matchers = X86ASTMatchers::new();
let source = "void foo() { }";
let file = Path::new("test.c");
let predicate = X86ASTMatcherPredicate::AllOf(vec![
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::FunctionDecl),
X86ASTMatcherPredicate::HasName("foo".into()),
]);
let matches = matchers.match_in_source(source, &predicate, file);
assert!(!matches.is_empty());
}
#[test]
fn test_match_in_source_any_of() {
let matchers = X86ASTMatchers::new();
let source = "int x = 5;\nvoid foo() { }";
let file = Path::new("test.c");
let predicate = X86ASTMatcherPredicate::AnyOf(vec![
X86ASTMatcherPredicate::HasName("x".into()),
X86ASTMatcherPredicate::HasName("foo".into()),
]);
let matches = matchers.match_in_source(source, &predicate, file);
assert!(matches.len() >= 2);
}
#[test]
fn test_match_in_source_has_descendant() {
let matchers = X86ASTMatchers::new();
let source = "void foo() { int x = 5; return x; }";
let file = Path::new("test.c");
let predicate = X86ASTMatcherPredicate::HasDescendant(Box::new(
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ReturnStmt),
));
let matches = matchers.match_in_source(source, &predicate, file);
assert!(!matches.is_empty());
}
#[test]
fn test_run_on_file() {
let path = temp_file("test_matcher.c", "void foo() { }\nint bar() { return 0; }");
let mut matchers = X86ASTMatchers::new();
matchers.add_matcher(X86ASTMatchers::function_decl());
matchers.add_matcher(X86ASTMatchers::has_name("bar"));
let result = matchers.run_on_file(&path);
assert!(result.success);
assert!(matchers.match_count() > 0);
fs::remove_file(&path).ok();
}
#[test]
fn test_run_on_nonexistent_file() {
let mut matchers = X86ASTMatchers::new();
matchers.add_matcher(X86ASTMatchers::function_decl());
let result = matchers.run_on_file(Path::new("nonexistent_file.c"));
assert!(!result.success);
}
#[test]
fn test_filter_by_kind() {
let mut matchers = X86ASTMatchers::new();
let source = "void foo() { }\nint x = 5;\nif (x) { }";
let file = Path::new("test.c");
matchers.match_in_source(
source,
&X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::Any),
file,
);
let func_matches = matchers.filter_by_kind(X86MatchedNodeKind::FunctionDecl);
}
#[test]
fn test_filter_by_name() {
let mut matchers = X86ASTMatchers::new();
let source = "void foo() { }";
let file = Path::new("test.c");
matchers.match_in_source(source, &X86ASTMatcherPredicate::HasName("foo".into()), file);
let filtered = matchers.filter_by_name("foo");
assert!(!filtered.is_empty());
}
#[test]
fn test_count_by_kind() {
let mut matchers = X86ASTMatchers::new();
let source = "void func1() { }\nvoid func2() { }";
let file = Path::new("test.c");
matchers.add_matcher(X86ASTMatchers::function_decl());
matchers.run_on_file(file);
let _count = matchers.count_by_kind(X86MatchedNodeKind::FunctionDecl);
}
#[test]
fn test_clear_matchers() {
let mut matchers = X86ASTMatchers::new();
matchers.add_matcher(X86ASTMatchers::function_decl());
matchers.clear();
assert_eq!(matchers.matchers.len(), 0);
assert_eq!(matchers.match_count(), 0);
}
#[test]
fn test_ast_match_display() {
let m = X86ASTMatch::new(
X86MatchedNodeKind::FunctionDecl,
X86ToolingLocation::new(1, 1),
PathBuf::from("test.c"),
)
.with_name("foo".into());
let s = format!("{}", m);
assert!(s.contains("functionDecl"));
assert!(s.contains("foo"));
assert!(s.contains("1:1"));
}
#[test]
fn test_ast_match_with_binding() {
let m = X86ASTMatch::new(
X86MatchedNodeKind::CallExpr,
X86ToolingLocation::new(1, 1),
PathBuf::from("test.c"),
)
.with_binding("callee", "printf")
.with_binding("arg", "\"hello\"");
assert_eq!(m.bindings.len(), 2);
assert_eq!(m.bindings.get("callee").unwrap(), "printf");
}
#[test]
fn test_matchers_count_100_plus() {
let mut count = 0;
let _ = X86ASTMatchers::function_decl();
count += 1;
let _ = X86ASTMatchers::var_decl();
count += 1;
let _ = X86ASTMatchers::record_decl();
count += 1;
let _ = X86ASTMatchers::enum_decl();
count += 1;
let _ = X86ASTMatchers::cxx_record_decl();
count += 1;
let _ = X86ASTMatchers::class_template_decl();
count += 1;
let _ = X86ASTMatchers::namespace_decl();
count += 1;
let _ = X86ASTMatchers::using_decl();
count += 1;
let _ = X86ASTMatchers::typedef_decl();
count += 1;
let _ = X86ASTMatchers::field_decl();
count += 1;
let _ = X86ASTMatchers::parm_var_decl();
count += 1;
let _ = X86ASTMatchers::cxx_method_decl();
count += 1;
let _ = X86ASTMatchers::cxx_constructor_decl();
count += 1;
let _ = X86ASTMatchers::cxx_destructor_decl();
count += 1;
let _ = X86ASTMatchers::function_template_decl();
count += 1;
let _ = X86ASTMatchers::class_template_specialization_decl();
count += 1;
let _ = X86ASTMatchers::is_definition();
count += 1;
let _ = X86ASTMatchers::is_explicit();
count += 1;
let _ = X86ASTMatchers::is_virtual();
count += 1;
let _ = X86ASTMatchers::is_override();
count += 1;
let _ = X86ASTMatchers::is_final();
count += 1;
let _ = X86ASTMatchers::is_const();
count += 1;
let _ = X86ASTMatchers::is_volatile();
count += 1;
let _ = X86ASTMatchers::is_static();
count += 1;
let _ = X86ASTMatchers::is_inline();
count += 1;
let _ = X86ASTMatchers::is_public();
count += 1;
let _ = X86ASTMatchers::is_protected();
count += 1;
let _ = X86ASTMatchers::is_private();
count += 1;
let _ = X86ASTMatchers::is_expanded_from_macro();
count += 1;
let _ = X86ASTMatchers::is_implicit();
count += 1;
let _ = X86ASTMatchers::is_defaulted();
count += 1;
let _ = X86ASTMatchers::is_deleted();
count += 1;
let _ = X86ASTMatchers::is_no_throw();
count += 1;
let _ = X86ASTMatchers::is_constexpr();
count += 1;
let _ = X86ASTMatchers::is_mutable();
count += 1;
let _ = X86ASTMatchers::has_name("x");
count += 1;
let _ = X86ASTMatchers::matches_name("x");
count += 1;
let _ = X86ASTMatchers::has_type("int");
count += 1;
let _ = X86ASTMatchers::has_return_type("void");
count += 1;
let _ = X86ASTMatchers::has_ancestor(X86ASTMatchers::anything());
count += 1;
let _ = X86ASTMatchers::has_descendant(X86ASTMatchers::anything());
count += 1;
let _ = X86ASTMatchers::has_parent(X86ASTMatchers::anything());
count += 1;
let _ = X86ASTMatchers::has_initializer(X86ASTMatchers::anything());
count += 1;
let _ = X86ASTMatchers::has_body(X86ASTMatchers::anything());
count += 1;
let _ = X86ASTMatchers::call_expr();
count += 1;
let _ = X86ASTMatchers::cxx_member_call_expr();
count += 1;
let _ = X86ASTMatchers::cxx_operator_call_expr();
count += 1;
let _ = X86ASTMatchers::decl_ref_expr();
count += 1;
let _ = X86ASTMatchers::member_expr();
count += 1;
let _ = X86ASTMatchers::integer_literal();
count += 1;
let _ = X86ASTMatchers::float_literal();
count += 1;
let _ = X86ASTMatchers::character_literal();
count += 1;
let _ = X86ASTMatchers::string_literal();
count += 1;
assert!(
count >= 50,
"Should have at least 50 matcher constructors (counted: {})",
count
);
}
#[test]
fn test_ast_matchers_default() {
let m = X86ASTMatchers::default();
assert_eq!(m.match_count(), 0);
}
#[test]
fn test_refactoring_edit_kinds() {
let insert = X86RefactoringEdit::insert(
PathBuf::from("test.c"),
X86ToolingLocation::new(1, 1),
"hello".into(),
"insert greeting".into(),
);
assert!(matches!(insert.op, X86RefactoringOpKind::Insert));
let remove = X86RefactoringEdit::remove(
PathBuf::from("test.c"),
X86ToolingRange::new(
X86ToolingLocation::new(1, 1),
X86ToolingLocation::new(1, 10),
),
"remove code".into(),
);
assert!(matches!(remove.op, X86RefactoringOpKind::Remove));
let replace = X86RefactoringEdit::replace(
PathBuf::from("test.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 5)),
"new".into(),
"replace text".into(),
);
assert!(matches!(replace.op, X86RefactoringOpKind::Replace));
}
#[test]
fn test_refactoring_edit_to_fixit() {
let edit = X86RefactoringEdit::replace(
PathBuf::from("test.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 5)),
"new".into(),
"fix".into(),
);
let fixit = edit.to_fixit();
assert_eq!(fixit.replacement_text, "new");
}
#[test]
fn test_refactoring_edit_to_replacement() {
let edit = X86RefactoringEdit::replace(
PathBuf::from("test.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 5)),
"X".into(),
"change".into(),
);
let repl = edit.to_replacement();
assert_eq!(repl.replacement_text, "X");
}
#[test]
fn test_refactoring_edit_display() {
let edit = X86RefactoringEdit::insert(
PathBuf::from("test.c"),
X86ToolingLocation::new(1, 1),
"text".into(),
"desc".into(),
);
let s = format!("{}", edit);
assert!(s.contains("INSERT"));
assert!(s.contains("test.c"));
}
#[test]
fn test_refactoring_tool_new() {
let rt = X86RefactoringTool::new();
assert!(rt.edits.is_empty());
assert_eq!(rt.edit_count(), 0);
}
#[test]
fn test_refactoring_tool_add_insert() {
let mut rt = X86RefactoringTool::new();
rt.insert(
Path::new("test.c"),
X86ToolingLocation::new(1, 1),
"#include <stdio.h>\n",
"add include",
);
assert_eq!(rt.edit_count(), 1);
}
#[test]
fn test_refactoring_tool_add_remove() {
let mut rt = X86RefactoringTool::new();
rt.remove(
Path::new("test.c"),
X86ToolingRange::new(
X86ToolingLocation::new(1, 1),
X86ToolingLocation::new(1, 10),
),
"remove unused code",
);
assert_eq!(rt.edit_count(), 1);
}
#[test]
fn test_refactoring_tool_add_replace() {
let mut rt = X86RefactoringTool::new();
rt.replace(
Path::new("test.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 5)),
"new_code",
"replace old code",
);
assert_eq!(rt.edit_count(), 1);
}
#[test]
fn test_refactoring_tool_coordinate_header_source() {
let mut rt = X86RefactoringTool::new();
let result = rt.coordinate_header_source(Path::new("test.h"), Path::new("test.c"));
assert!(result.is_ok());
assert_eq!(rt.header_source_map.len(), 2);
}
#[test]
fn test_refactoring_tool_generate_fixits() {
let mut rt = X86RefactoringTool::new();
rt.insert(
Path::new("test.c"),
X86ToolingLocation::new(1, 1),
"code",
"insert",
);
let fixits = rt.generate_fixits();
assert_eq!(fixits.len(), 1);
}
#[test]
fn test_refactoring_tool_execute_all_empty() {
let mut rt = X86RefactoringTool::new();
let result = rt.execute_all();
assert!(result.success);
assert_eq!(result.files_processed, 0);
}
#[test]
fn test_refactoring_tool_clear() {
let mut rt = X86RefactoringTool::new();
rt.insert(
Path::new("test.c"),
X86ToolingLocation::new(1, 1),
"code",
"insert",
);
rt.clear();
assert_eq!(rt.edit_count(), 0);
assert!(rt.fixit_hints.is_empty());
}
#[test]
fn test_refactoring_tool_dry_run() {
let mut rt = X86RefactoringTool::new();
rt.dry_run = true;
rt.insert(
Path::new("test.c"),
X86ToolingLocation::new(1, 1),
"code",
"insert",
);
let _result = rt.execute_all();
let saved = rt.save_all();
assert!(saved.is_ok());
}
#[test]
fn test_refactoring_tool_default() {
let rt = X86RefactoringTool::default();
assert_eq!(rt.edit_count(), 0);
}
#[test]
fn test_find_symbol_new() {
let fs = X86ToolingFindSymbol::new();
assert!(fs.search_symbol.is_none());
assert_eq!(fs.reference_count(), 0);
}
#[test]
fn test_find_symbol_set_symbol() {
let mut fs = X86ToolingFindSymbol::new();
fs.set_symbol("my_func");
assert_eq!(fs.search_symbol, Some("my_func".to_string()));
}
#[test]
fn test_find_references_in_source() {
let fs = X86ToolingFindSymbol::new();
let source = "void foo() { foo(); bar(); foo(); }";
let file = Path::new("test.c");
let refs = fs.find_references_in_source(source, "foo", file);
assert_eq!(refs.len(), 3); }
#[test]
fn test_find_declarations_in_source() {
let fs = X86ToolingFindSymbol::new();
let source = "int my_var = 42;\nvoid my_func() { }\nint other = 7;";
let file = Path::new("test.c");
let decls = fs.find_declarations_in_source(source, "my_func", file);
assert!(!decls.is_empty());
assert!(decls
.iter()
.any(|d| d.ref_kind == X86SymbolRefKind::Definition));
}
#[test]
fn test_find_overrides_in_source() {
let fs = X86ToolingFindSymbol::new();
let source = "virtual void bar() override { }";
let file = Path::new("test.cpp");
let overs = fs.find_overrides_in_source(source, "bar", file);
assert!(!overs.is_empty());
}
#[test]
fn test_find_class_hierarchy() {
let fs = X86ToolingFindSymbol::new();
let source = "class Derived : public Base { };\nclass Base { };";
let file = Path::new("test.cpp");
let (bases, derived) = fs.find_class_hierarchy_in_source(source, "Base", file);
assert!(!bases.is_empty() || !derived.is_empty());
}
#[test]
fn test_classify_reference_line_call() {
let fs = X86ToolingFindSymbol::new();
let line = "foo(arg1, arg2);";
let kind = fs.classify_reference_line(line, "foo");
assert_eq!(kind, X86SymbolRefKind::Call);
}
#[test]
fn test_classify_reference_line_declaration() {
let fs = X86ToolingFindSymbol::new();
let line = "void foo(int x);";
let kind = fs.classify_reference_line(line, "foo");
assert_eq!(kind, X86SymbolRefKind::Declaration);
}
#[test]
fn test_symbol_ref_kind_display() {
assert_eq!(format!("{}", X86SymbolRefKind::Definition), "definition");
assert_eq!(format!("{}", X86SymbolRefKind::Call), "call");
assert_eq!(format!("{}", X86SymbolRefKind::Override), "override");
assert_eq!(format!("{}", X86SymbolRefKind::BaseClass), "base_class");
}
#[test]
fn test_symbol_ref_display() {
let r = X86SymbolRef::new(
"foo".into(),
X86SymbolRefKind::Call,
PathBuf::from("test.c"),
X86ToolingLocation::new(5, 3),
);
let s = format!("{}", r);
assert!(s.contains("foo"));
assert!(s.contains("test.c"));
}
#[test]
fn test_run_on_file_find_symbol() {
let path = temp_file(
"test_find.c",
"void foo() { }\nint main() { foo(); return 0; }",
);
let mut fs = X86ToolingFindSymbol::new();
fs.set_symbol("foo");
let result = fs.run_on_file(&path);
assert!(result.success);
assert!(fs.reference_count() > 0);
fs::remove_file(&path).ok();
}
#[test]
fn test_run_on_nonexistent_file_find_symbol() {
let mut fs = X86ToolingFindSymbol::new();
fs.set_symbol("foo");
let result = fs.run_on_file(Path::new("nonexistent.c"));
assert!(!result.success);
}
#[test]
fn test_find_symbol_clear() {
let mut fs = X86ToolingFindSymbol::new();
fs.set_symbol("foo");
fs.clear();
assert!(fs.search_symbol.is_none());
assert_eq!(fs.reference_count(), 0);
}
#[test]
fn test_find_symbol_default() {
let fs = X86ToolingFindSymbol::default();
assert_eq!(fs.reference_count(), 0);
}
#[test]
fn test_code_quality_new() {
let cq = X86ToolingCodeQuality::new();
assert!(cq.complexity_metrics.is_empty());
assert!(cq.dead_code.is_empty());
assert!(cq.duplications.is_empty());
}
#[test]
fn test_complexity_metrics_new() {
let cm = X86ComplexityMetrics::new("test_func".into());
assert_eq!(cm.name, "test_func");
assert_eq!(cm.cyclomatic_complexity, 1);
assert_eq!(cm.cognitive_complexity, 0);
}
#[test]
fn test_complexity_metrics_compute_cyclomatic() {
let mut cm = X86ComplexityMetrics::new("func".into());
let source = vec![
"void func() {",
" if (x > 0) {",
" if (y > 0) {",
" return x + y;",
" }",
" }",
" for (int i = 0; i < 10; i++) {",
" doSomething();",
" }",
" return 0;",
"}",
];
cm.compute_cyclomatic(&source);
assert!(cm.cyclomatic_complexity >= 3);
}
#[test]
fn test_complexity_metrics_compute_cognitive() {
let mut cm = X86ComplexityMetrics::new("func".into());
let source = vec![
"void func() {",
" if (x) {", " if (y) {", " return;",
" }",
" }",
" while (z) {", " doWork();",
" }",
"}",
];
cm.compute_cognitive(&source);
assert!(cm.cognitive_complexity >= 5);
}
#[test]
fn test_complexity_metrics_compute_halstead() {
let mut cm = X86ComplexityMetrics::new("func".into());
let source = vec![
"int func(int a, int b) {",
" int c = a + b;",
" return c;",
"}",
];
cm.compute_halstead(&source);
assert!(cm.halstead_volume > 0.0 || cm.halstead_volume == 0.0);
}
#[test]
fn test_complexity_metrics_display() {
let cm = X86ComplexityMetrics::new("my_func".into());
let s = format!("{}", cm);
assert!(s.contains("my_func"));
assert!(s.contains("Cyclomatic"));
assert!(s.contains("Cognitive"));
assert!(s.contains("Halstead"));
}
#[test]
fn test_dead_code_kind_display() {
assert_eq!(
format!("{}", X86DeadCodeKind::UnusedFunction),
"unused-function"
);
assert_eq!(
format!("{}", X86DeadCodeKind::UnusedParameter),
"unused-parameter"
);
assert_eq!(
format!("{}", X86DeadCodeKind::UnreachableCode),
"unreachable-code"
);
}
#[test]
fn test_analyze_file_complexity() {
let path = temp_file(
"test_quality.c",
"int simple() { return 0; }\n\
int complex(int x, int y) {\n\
if (x > 0) {\n\
if (y > 0) { return x + y; }\n\
for (int i = 0; i < 10; i++) { x++; }\n\
}\n\
while (x > 0) { x--; }\n\
return 0;\n\
}",
);
let mut cq = X86ToolingCodeQuality::new();
let result = cq.analyze_file(&path);
assert!(result.success);
assert!(!cq.complexity_metrics.is_empty());
fs::remove_file(&path).ok();
}
#[test]
fn test_analyze_file_dead_code() {
let path = temp_file(
"test_dead.c",
"int used_func() { return 42; }\n\
int unused_func() { return 0; }\n\
int main() { return used_func(); }",
);
let mut cq = X86ToolingCodeQuality::new();
let result = cq.analyze_file(&path);
assert!(result.success);
fs::remove_file(&path).ok();
}
#[test]
fn test_analyze_file_duplication() {
let path = temp_file(
"test_dup.c",
"int a() { int x = 1; x = x + 1; x = x * 2; return x; }\n\
int b() { int y = 1; y = y + 1; y = y * 2; return y; }",
);
let mut cq = X86ToolingCodeQuality::new();
cq.duplication_threshold = 0.5;
let result = cq.analyze_file(&path);
assert!(result.success);
fs::remove_file(&path).ok();
}
#[test]
fn test_analyze_nonexistent_file_quality() {
let mut cq = X86ToolingCodeQuality::new();
let result = cq.analyze_file(Path::new("nonexistent.c"));
assert!(!result.success);
}
#[test]
fn test_generate_report() {
let mut cq = X86ToolingCodeQuality::new();
let path = temp_file("test_report.c", "int main() { return 0; }");
cq.analyze_file(&path);
let report = cq.generate_report(&path);
assert!(report.contains("Code Quality Report"));
assert!(report.contains("Complexity"));
assert!(report.contains("Dead Code"));
assert!(report.contains("Duplication"));
fs::remove_file(&path).ok();
}
#[test]
fn test_code_quality_clear() {
let mut cq = X86ToolingCodeQuality::new();
let path = temp_file("test_clear.c", "int main() { return 0; }");
cq.analyze_file(&path);
cq.clear();
assert!(cq.complexity_metrics.is_empty());
assert!(cq.dead_code.is_empty());
assert!(cq.duplications.is_empty());
fs::remove_file(&path).ok();
}
#[test]
fn test_code_quality_default() {
let cq = X86ToolingCodeQuality::default();
assert_eq!(cq.duplication_threshold, 0.75);
}
#[test]
fn test_extract_function_name() {
let cq = X86ToolingCodeQuality::new();
assert_eq!(cq.extract_function_name("void foo(int x) {"), "foo");
assert_eq!(cq.extract_function_name("int bar ( float y ) {"), "bar");
assert_eq!(cq.extract_function_name("static inline int baz() {"), "baz");
}
#[test]
fn test_count_parameters() {
let cq = X86ToolingCodeQuality::new();
assert_eq!(cq.count_parameters("void foo()"), 0);
assert_eq!(cq.count_parameters("void foo(void)"), 0);
assert_eq!(cq.count_parameters("int bar(int a, int b)"), 2);
assert_eq!(cq.count_parameters("int baz(int a, float b, char c)"), 3);
}
#[test]
fn test_extract_name_from_decl() {
let cq = X86ToolingCodeQuality::new();
assert_eq!(
cq.extract_name_from_decl("void foo(int x)"),
Some("foo".into())
);
assert_eq!(cq.extract_name_from_decl("int x = 5;"), Some("x".into()));
}
#[test]
fn test_full_pipeline_on_file() {
let path = temp_file(
"test_pipeline.c",
"int add(int a, int b) { return a + b; }\n\
int unused() { return 0; }\n\
int main() { return add(1, 2); }",
);
let mut tf = X86ToolingFull::new();
tf.add_file(path.clone());
let _ = tf.run_ast_matching();
let _ = tf.run_code_quality();
let results = tf.run_all();
assert!(!results.is_empty());
let summary = tf.generate_summary();
assert_eq!(summary.total_files, 1);
fs::remove_file(&path).ok();
}
#[test]
fn test_lib_tooling_run_on_file() {
let path = temp_file("test_libtooling.c", "int main() { return 0; }");
let mut lt = X86LibTooling::new();
let action = X86FrontendAction::new(X86FrontendActionKind::SyntaxOnly, path.clone());
let result = lt.run_action(action);
assert!(result.success);
fs::remove_file(&path).ok();
}
#[test]
fn test_ast_matchers_and_find_symbol_integration() {
let path = temp_file(
"test_integration.c",
"void helper() { }\n\
int main() {\n\
helper();\n\
return 0;\n\
}",
);
let mut fs = X86ToolingFindSymbol::new();
fs.set_symbol("helper");
let result = fs.run_on_file(&path);
assert!(result.success);
assert!(fs.reference_count() > 0);
let mut matchers = X86ASTMatchers::new();
matchers.add_matcher(X86ASTMatchers::function_decl());
let result = matchers.run_on_file(&path);
assert!(result.success);
fs::remove_file(&path).ok();
}
#[test]
fn test_refactoring_and_quality_integration() {
let path = temp_file(
"test_ref_qual.c",
"int calculate(int a, int b) {\n\
if (a > b) {\n\
if (a > 100) {\n\
return a - b;\n\
}\n\
}\n\
return a + b;\n\
}",
);
let mut cq = X86ToolingCodeQuality::new();
let result = cq.analyze_file(&path);
assert!(result.success);
let mut rt = X86RefactoringTool::new();
rt.insert(
&path,
X86ToolingLocation::new(1, 1),
"// Auto-generated comment\n",
"add comment",
);
let _ = rt.execute_all();
fs::remove_file(&path).ok();
}
#[test]
fn test_tooling_result_accumulation() {
let mut result = X86ToolingResult::new();
result.files_processed = 5;
result.files_modified = 3;
result.success = false;
assert_eq!(result.files_processed, 5);
assert_eq!(result.files_modified, 3);
assert!(!result.success);
}
#[test]
fn test_multiple_matchers_on_source() {
let mut matchers = X86ASTMatchers::new();
matchers.add_matcher(X86ASTMatchers::has_name("main"));
matchers.add_matcher(X86ASTMatchers::is_static());
matchers.add_matcher(X86ASTMatchers::is_constexpr());
let source = "static constexpr int MAX = 100;\nint main() { return 0; }";
let file = Path::new("multi.c");
let _ = matchers.match_in_source(source, &matchers.matchers[0], file);
}
#[test]
fn test_find_symbol_get_all() {
let fs = X86ToolingFindSymbol::new();
assert!(fs.get_references().is_empty());
assert!(fs.get_declarations().is_empty());
assert!(fs.get_overrides().is_empty());
assert!(fs.get_base_classes().is_empty());
assert!(fs.get_derived_classes().is_empty());
}
#[test]
fn test_code_quality_get_all() {
let cq = X86ToolingCodeQuality::new();
assert!(cq.get_complexity_metrics().is_empty());
assert!(cq.get_dead_code().is_empty());
assert!(cq.get_duplications().is_empty());
}
#[test]
fn test_variant_coverage_all_node_kinds() {
let kinds = [
X86MatchedNodeKind::FunctionDecl,
X86MatchedNodeKind::VarDecl,
X86MatchedNodeKind::RecordDecl,
X86MatchedNodeKind::EnumDecl,
X86MatchedNodeKind::CXXRecordDecl,
X86MatchedNodeKind::ClassTemplateDecl,
X86MatchedNodeKind::NamespaceDecl,
X86MatchedNodeKind::UsingDecl,
X86MatchedNodeKind::TypedefDecl,
X86MatchedNodeKind::FieldDecl,
X86MatchedNodeKind::ParmVarDecl,
X86MatchedNodeKind::CXXMethodDecl,
X86MatchedNodeKind::CXXConstructorDecl,
X86MatchedNodeKind::CXXDestructorDecl,
X86MatchedNodeKind::FunctionTemplateDecl,
X86MatchedNodeKind::ClassTemplateSpecializationDecl,
X86MatchedNodeKind::CallExpr,
X86MatchedNodeKind::IfStmt,
X86MatchedNodeKind::ForStmt,
X86MatchedNodeKind::WhileStmt,
X86MatchedNodeKind::ReturnStmt,
X86MatchedNodeKind::CXXTryStmt,
X86MatchedNodeKind::LambdaExpr,
X86MatchedNodeKind::PointerType,
X86MatchedNodeKind::ReferenceType,
X86MatchedNodeKind::Any,
];
for kind in &kinds {
let s = format!("{}", kind);
assert!(!s.is_empty(), "Display for {:?} should not be empty", kind);
}
}
#[test]
fn test_variant_coverage_all_frontend_action_kinds() {
let kinds = [
X86FrontendActionKind::SyntaxOnly,
X86FrontendActionKind::ASTBuild,
X86FrontendActionKind::EmitLLVM,
X86FrontendActionKind::EmitAssembly,
X86FrontendActionKind::EmitObj,
X86FrontendActionKind::EmitFull,
X86FrontendActionKind::PluginAction,
X86FrontendActionKind::Custom,
];
for kind in &kinds {
let s = format!("{}", kind);
assert!(!s.is_empty());
}
}
#[test]
fn test_variant_coverage_all_severity_levels() {
let levels = [
X86ToolingSeverity::Ignored,
X86ToolingSeverity::Note,
X86ToolingSeverity::Warning,
X86ToolingSeverity::Error,
X86ToolingSeverity::Fatal,
];
for level in &levels {
let s = format!("{}", level);
assert!(!s.is_empty());
}
}
#[test]
fn test_variant_coverage_all_symbol_ref_kinds() {
let kinds = [
X86SymbolRefKind::Definition,
X86SymbolRefKind::Declaration,
X86SymbolRefKind::Reference,
X86SymbolRefKind::Call,
X86SymbolRefKind::Override,
X86SymbolRefKind::BaseClass,
X86SymbolRefKind::DerivedClass,
X86SymbolRefKind::FriendDecl,
X86SymbolRefKind::TemplateSpecialization,
];
for kind in &kinds {
let s = format!("{}", kind);
assert!(!s.is_empty());
}
}
#[test]
fn test_variant_coverage_all_dead_code_kinds() {
let kinds = [
X86DeadCodeKind::UnusedFunction,
X86DeadCodeKind::UnusedVariable,
X86DeadCodeKind::UnusedParameter,
X86DeadCodeKind::UnreachableCode,
X86DeadCodeKind::DeadAssignment,
];
for kind in &kinds {
let s = format!("{}", kind);
assert!(!s.is_empty());
}
}
#[test]
fn test_variant_coverage_all_execution_modes() {
let modes = [
X86ToolExecutionMode::Standalone,
X86ToolExecutionMode::MapReduce,
X86ToolExecutionMode::Parallel,
];
for mode in &modes {
let s = format!("{:?}", mode);
assert!(!s.is_empty());
}
}
#[test]
fn test_all_tooling_structures_default() {
let _ = X86ToolingFull::default();
let _ = X86LibTooling::default();
let _ = X86ASTMatchers::default();
let _ = X86RefactoringTool::default();
let _ = X86ToolingFindSymbol::default();
let _ = X86ToolingCodeQuality::default();
let _ = X86CompilationDatabase::default();
let _ = X86ToolingResult::default();
}
#[test]
fn test_has_parameter_and_any_parameter() {
let m1 = X86ASTMatchers::has_parameter(0, X86ASTMatchers::anything());
let qs = m1.to_query_string();
assert!(qs.contains("hasParameter"));
let m2 = X86ASTMatchers::has_any_parameter(X86ASTMatchers::anything());
let qs = m2.to_query_string();
assert!(qs.contains("hasAnyParameter"));
}
#[test]
fn test_has_initializer_and_body() {
let m1 = X86ASTMatchers::has_initializer(X86ASTMatchers::integer_literal());
assert!(m1.to_query_string().contains("hasInitializer"));
let m2 = X86ASTMatchers::has_body(X86ASTMatchers::return_stmt());
assert!(m2.to_query_string().contains("hasBody"));
}
#[test]
fn test_has_canonical_type() {
let m = X86ASTMatchers::has_canonical_type(X86ASTMatchers::is_integer());
assert!(m.to_query_string().contains("hasCanonicalType"));
}
#[test]
fn test_for_each_and_find_all() {
let m1 = X86ASTMatchers::for_each(X86ASTMatchers::anything());
assert!(m1.to_query_string().contains("forEach"));
let m2 = X86ASTMatchers::for_each_descendant(X86ASTMatchers::anything());
assert!(m2.to_query_string().contains("forEachDescendant"));
let m3 = X86ASTMatchers::find_all(X86ASTMatchers::call_expr());
assert!(m3.to_query_string().contains("findAll"));
}
#[test]
fn test_equals_node() {
let m = X86ASTMatchers::equals_node("some_node");
assert!(m.to_query_string().contains("equalsNode"));
assert!(m.to_query_string().contains("some_node"));
}
#[test]
fn test_is_public_protected_private() {
assert_eq!(X86ASTMatchers::is_public().to_query_string(), "isPublic()");
assert_eq!(
X86ASTMatchers::is_protected().to_query_string(),
"isProtected()"
);
assert_eq!(
X86ASTMatchers::is_private().to_query_string(),
"isPrivate()"
);
}
#[test]
fn test_is_no_throw_is_defaulted_is_deleted() {
assert_eq!(
X86ASTMatchers::is_no_throw().to_query_string(),
"isNoThrow()"
);
assert_eq!(
X86ASTMatchers::is_defaulted().to_query_string(),
"isDefaulted()"
);
assert_eq!(
X86ASTMatchers::is_deleted().to_query_string(),
"isDeleted()"
);
}
#[test]
fn test_is_expanded_from_macro_is_implicit() {
assert_eq!(
X86ASTMatchers::is_expanded_from_macro().to_query_string(),
"isExpandedFromMacro()"
);
assert_eq!(
X86ASTMatchers::is_implicit().to_query_string(),
"isImplicit()"
);
}
#[test]
fn test_is_mutable_is_explicit_is_final_override() {
assert_eq!(
X86ASTMatchers::is_mutable().to_query_string(),
"isMutable()"
);
assert_eq!(
X86ASTMatchers::is_explicit().to_query_string(),
"isExplicit()"
);
assert_eq!(X86ASTMatchers::is_final().to_query_string(), "isFinal()");
assert_eq!(
X86ASTMatchers::is_override().to_query_string(),
"isOverride()"
);
}
#[test]
fn test_is_volatile_is_volatile_qualified() {
assert_eq!(
X86ASTMatchers::is_volatile().to_query_string(),
"isVolatile()"
);
assert_eq!(
X86ASTMatchers::is_volatile_qualified().to_query_string(),
"isVolatileQualified()"
);
}
#[test]
fn test_has_type_has_return_type() {
assert_eq!(
X86ASTMatchers::has_type("int").to_query_string(),
"hasType(\"int\")"
);
assert_eq!(
X86ASTMatchers::has_return_type("void").to_query_string(),
"hasReturnType(\"void\")"
);
}
#[test]
fn test_matches_name() {
assert_eq!(
X86ASTMatchers::matches_name("test_.*").to_query_string(),
"matchesName(\"test_.*\")"
);
}
#[test]
fn test_all_cast_exprs() {
let casts = [
X86ASTMatchers::cast_expr(),
X86ASTMatchers::explicit_cast_expr(),
X86ASTMatchers::c_style_cast_expr(),
X86ASTMatchers::static_cast_expr(),
X86ASTMatchers::dynamic_cast_expr(),
X86ASTMatchers::const_cast_expr(),
X86ASTMatchers::reinterpret_cast_expr(),
X86ASTMatchers::implicit_cast_expr(),
];
for c in &casts {
assert!(!c.to_query_string().is_empty());
}
}
#[test]
fn test_all_advanced_exprs() {
let exprs = [
X86ASTMatchers::materialize_temporary_expr(),
X86ASTMatchers::array_subscript_expr(),
X86ASTMatchers::init_list_expr(),
X86ASTMatchers::paren_list_expr(),
X86ASTMatchers::lambda_expr(),
X86ASTMatchers::new_expr(),
X86ASTMatchers::delete_expr(),
X86ASTMatchers::this_expr(),
X86ASTMatchers::default_arg_expr(),
X86ASTMatchers::cxx_construct_expr(),
X86ASTMatchers::cxx_temporary_object_expr(),
X86ASTMatchers::cxx_bind_temporary_expr(),
X86ASTMatchers::cxx_functional_cast_expr(),
];
for e in &exprs {
assert!(!e.to_query_string().is_empty());
}
}
#[test]
fn test_all_type_matchers() {
let types = [
X86ASTMatchers::as_string("int"),
X86ASTMatchers::is_integer(),
X86ASTMatchers::is_floating(),
X86ASTMatchers::is_pointer(),
X86ASTMatchers::is_reference(),
X86ASTMatchers::is_const_qualified(),
X86ASTMatchers::pointer_type(),
X86ASTMatchers::reference_type(),
X86ASTMatchers::array_type(),
X86ASTMatchers::elaborated_type(),
X86ASTMatchers::typedef_type(),
X86ASTMatchers::template_specialization_type(),
X86ASTMatchers::auto_type(),
X86ASTMatchers::decayed_type(),
X86ASTMatchers::paren_type(),
X86ASTMatchers::attributed_type(),
];
for t in &types {
assert!(!t.to_query_string().is_empty());
}
}
#[test]
fn test_all_stmt_matchers() {
let stmts = [
X86ASTMatchers::if_stmt(),
X86ASTMatchers::for_stmt(),
X86ASTMatchers::while_stmt(),
X86ASTMatchers::do_stmt(),
X86ASTMatchers::switch_stmt(),
X86ASTMatchers::return_stmt(),
X86ASTMatchers::break_stmt(),
X86ASTMatchers::continue_stmt(),
X86ASTMatchers::goto_stmt(),
X86ASTMatchers::compound_stmt(),
X86ASTMatchers::decl_stmt(),
X86ASTMatchers::null_stmt(),
X86ASTMatchers::case_stmt(),
X86ASTMatchers::default_stmt(),
X86ASTMatchers::label_stmt(),
X86ASTMatchers::cxx_try_stmt(),
X86ASTMatchers::cxx_catch_stmt(),
X86ASTMatchers::cxx_throw_expr(),
X86ASTMatchers::cxx_for_range_stmt(),
];
for s in &stmts {
assert!(!s.to_query_string().is_empty());
}
}
#[test]
fn test_refactoring_edit_operations_count() {
let mut rt = X86RefactoringTool::new();
rt.insert(
Path::new("a.c"),
X86ToolingLocation::new(1, 1),
"X",
"insert",
);
rt.remove(
Path::new("b.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 5)),
"remove",
);
rt.replace(
Path::new("c.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 3)),
"Y",
"replace",
);
assert_eq!(rt.edit_count(), 3);
}
#[test]
fn test_replacement_apply_edge_cases() {
let r = X86ToolingReplacement::new(
PathBuf::from("test.c"),
X86ToolingRange::new(
X86ToolingLocation::new(100, 1),
X86ToolingLocation::new(100, 5),
),
"X".into(),
"out of bounds".into(),
);
let source = "abc\ndef\n";
assert!(r.apply(source).is_none());
let r2 = X86ToolingReplacement::new(
PathBuf::from("test.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 1)),
"".into(),
"empty replacement".into(),
);
let result = r2.apply(source);
assert!(result.is_some());
}
#[test]
fn test_execute_map_reduce_multiple_actions() {
let mut exec = X86ToolExecutor::new(X86ToolExecutionMode::MapReduce);
exec.add_action(X86FrontendAction::new(
X86FrontendActionKind::SyntaxOnly,
PathBuf::from("nonexistent1.c"),
));
exec.add_action(X86FrontendAction::new(
X86FrontendActionKind::SyntaxOnly,
PathBuf::from("nonexistent2.c"),
));
let results = exec.execute();
assert_eq!(results.results.len(), 2);
}
#[test]
fn test_tooling_full_summary_display_full() {
let summary = X86ToolingFullSummary {
total_files: 10,
total_operations: 50,
total_errors: 3,
total_warnings: 12,
total_replacements: 25,
files_modified: 7,
};
let s = format!("{}", summary);
assert!(s.contains("10"));
assert!(s.contains("50"));
assert!(s.contains("3"));
assert!(s.contains("12"));
assert!(s.contains("25"));
assert!(s.contains("7"));
}
#[test]
fn test_compilation_db_with_arguments() {
let path = temp_file(
"compile_args.json",
r#"[
{
"directory": "/home/user/project",
"file": "src/main.cpp",
"arguments": ["clang++", "-std=c++17", "-Iinclude", "-DDEBUG", "-c", "src/main.cpp"],
"output": "build/main.o"
}
]"#,
);
let mut db = X86CompilationDatabase::new();
db.load_from_file(&path).ok();
assert_eq!(db.len(), 1);
let entry = db.lookup(Path::new("src/main.cpp"));
assert!(entry.is_some());
assert_eq!(
entry.unwrap().directory,
PathBuf::from("/home/user/project")
);
fs::remove_file(&path).ok();
}
#[test]
fn test_compilation_db_multiple_entries() {
let path = temp_file(
"compile_multi.json",
r#"[
{"directory": "/src", "file": "a.c", "command": "gcc -c a.c"},
{"directory": "/src", "file": "b.c", "command": "gcc -c b.c"},
{"directory": "/src", "file": "c.c", "command": "gcc -c c.c"}
]"#,
);
let mut db = X86CompilationDatabase::new();
db.load_from_file(&path).ok();
assert_eq!(db.len(), 3);
fs::remove_file(&path).ok();
}
#[test]
fn test_compilation_db_collect_include_paths_complex() {
let mut db = X86CompilationDatabase::new();
let mut e1 = X86CompilationDBEntry::new(PathBuf::from("/src"), PathBuf::from("a.c"));
e1.arguments = vec![
"cc".into(),
"-I/usr/local/include".into(),
"-I".into(),
"/opt/lib/include".into(),
"-I/usr/X11/include".into(),
];
db.entries.push(e1);
let paths = db.collect_include_paths();
assert!(paths.len() >= 3);
}
#[test]
fn test_match_in_source_has_string_value_no_match() {
let matchers = X86ASTMatchers::new();
let source = r#"const char* msg = "goodbye";"#;
let file = Path::new("test.c");
let predicate = X86ASTMatcherPredicate::HasStringValue("hello".into());
let matches = matchers.match_in_source(source, &predicate, file);
assert!(matches.is_empty());
}
#[test]
fn test_match_in_source_has_integer_value_no_match() {
let matchers = X86ASTMatchers::new();
let source = "int x = 99;";
let file = Path::new("test.c");
let predicate = X86ASTMatcherPredicate::HasIntegerValue(42);
let matches = matchers.match_in_source(source, &predicate, file);
assert!(matches.is_empty());
}
#[test]
fn test_match_in_source_is_definition() {
let matchers = X86ASTMatchers::new();
let source = "void foo() { }";
let file = Path::new("test.c");
let matches = matchers.match_in_source(source, &X86ASTMatcherPredicate::IsDefinition, file);
assert!(!matches.is_empty());
}
#[test]
fn test_match_in_source_expr_matchers() {
let matchers = X86ASTMatchers::new();
let source = "int x = foo(42);";
let file = Path::new("test.c");
let predicate = X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CallExpr);
let matches = matchers.match_in_source(source, &predicate, file);
assert!(!matches.is_empty());
}
#[test]
fn test_match_in_source_stmt_matchers() {
let matchers = X86ASTMatchers::new();
let source = "if (x > 0) { return x; } else { return -x; }";
let file = Path::new("test.c");
let predicate = X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::IfStmt);
let matches = matchers.match_in_source(source, &predicate, file);
assert!(!matches.is_empty());
}
#[test]
fn test_match_in_source_for_while_do_switch() {
let matchers = X86ASTMatchers::new();
let source =
"for (;;) { }\nwhile (1) { }\ndo { } while (0);\nswitch (x) { case 1: break; }";
let file = Path::new("test.c");
for kind in &[
X86MatchedNodeKind::ForStmt,
X86MatchedNodeKind::WhileStmt,
X86MatchedNodeKind::DoStmt,
X86MatchedNodeKind::SwitchStmt,
] {
let predicate = X86ASTMatcherPredicate::NodeKind(*kind);
let matches = matchers.match_in_source(source, &predicate, file);
assert!(!matches.is_empty(), "Should match {:?}", kind);
}
}
#[test]
fn test_extract_name_from_line() {
let matchers = X86ASTMatchers::new();
assert_eq!(matchers.extract_name_from_line("void foo() {"), "foo");
assert_eq!(matchers.extract_name_from_line("int bar(int x) {"), "bar");
assert_eq!(
matchers.extract_name_from_line("class MyClass {"),
"MyClass"
);
assert_eq!(matchers.extract_name_from_line("struct Data {"), "Data");
assert_eq!(matchers.extract_name_from_line("enum Color {"), "Color");
assert_eq!(matchers.extract_name_from_line("namespace ns {"), "ns");
assert_eq!(matchers.extract_name_from_line("// comment"), "");
}
#[test]
fn test_line_contains_decl_named() {
let matchers = X86ASTMatchers::new();
assert!(matchers.line_contains_decl_named("void foo() {", "foo"));
assert!(matchers.line_contains_decl_named("int bar;", "bar"));
assert!(!matchers.line_contains_decl_named("// void foo() {", "foo"));
assert!(!matchers.line_contains_decl_named(" x = foo();", "foo"));
}
#[test]
fn test_ast_match_builder() {
let m = X86ASTMatch::new(
X86MatchedNodeKind::CXXMethodDecl,
X86ToolingLocation::new(3, 5),
PathBuf::from("test.cpp"),
)
.with_name("doWork".into())
.with_range(X86ToolingRange::new(
X86ToolingLocation::new(3, 5),
X86ToolingLocation::new(3, 15),
))
.with_binding("class", "Worker")
.with_binding("access", "public");
assert_eq!(m.name, Some("doWork".into()));
assert!(m.range.is_some());
assert_eq!(m.bindings.len(), 2);
}
#[test]
fn test_refactoring_tool_has_modifications() {
let mut rt = X86RefactoringTool::new();
let path = PathBuf::from("nonexistent.c");
assert!(!rt.has_modifications(&path));
}
#[test]
fn test_refactoring_tool_get_original() {
let rt = X86RefactoringTool::new();
assert!(rt.get_original(Path::new("nonexistent.c")).is_none());
}
#[test]
fn test_refactoring_tool_get_modified() {
let rt = X86RefactoringTool::new();
assert!(rt.get_modified(Path::new("nonexistent.c")).is_none());
}
#[test]
fn test_refactoring_tool_multiple_edits_same_file() {
let mut rt = X86RefactoringTool::new();
rt.insert(
Path::new("test.c"),
X86ToolingLocation::new(1, 1),
"// header\n",
"add header",
);
rt.insert(
Path::new("test.c"),
X86ToolingLocation::new(3, 1),
"int x = 0;\n",
"add variable",
);
assert_eq!(rt.edit_count(), 2);
}
#[test]
fn test_refactoring_tool_edit_types_distinct() {
let insert = X86RefactoringEdit::insert(
PathBuf::from("test.c"),
X86ToolingLocation::new(1, 1),
"X".into(),
"ins".into(),
);
let remove = X86RefactoringEdit::remove(
PathBuf::from("test.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 5)),
"rem".into(),
);
let replace = X86RefactoringEdit::replace(
PathBuf::from("test.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 3)),
"Y".into(),
"rep".into(),
);
assert!(matches!(insert.op, X86RefactoringOpKind::Insert));
assert!(matches!(remove.op, X86RefactoringOpKind::Remove));
assert!(matches!(replace.op, X86RefactoringOpKind::Replace));
}
#[test]
fn test_find_symbol_multiple_files() {
let mut fs = X86ToolingFindSymbol::new();
fs.set_symbol("helper");
let src1 = "void helper() { }\nint main() { helper(); }";
let src2 = "extern void helper();\nvoid other() { helper(); }";
let f1 = Path::new("a.c");
let f2 = Path::new("b.c");
let refs1 = fs.find_references_in_source(src1, "helper", f1);
let refs2 = fs.find_references_in_source(src2, "helper", f2);
assert!(refs1.len() >= 2);
assert!(refs2.len() >= 2);
}
#[test]
fn test_find_symbol_declarations_vs_definitions() {
let fs = X86ToolingFindSymbol::new();
let source = "void proto();\nvoid proto() { }";
let file = Path::new("test.c");
let decls = fs.find_declarations_in_source(source, "proto", file);
assert!(decls.len() >= 1);
let has_def = decls
.iter()
.any(|d| d.ref_kind == X86SymbolRefKind::Definition);
assert!(has_def);
}
#[test]
fn test_find_symbol_templates() {
let fs = X86ToolingFindSymbol::new();
let source = "template<typename T> T max(T a, T b) { return a > b ? a : b; }";
let file = Path::new("test.cpp");
let refs = fs.find_references_in_source(source, "max", file);
assert!(!refs.is_empty());
let is_template = refs
.iter()
.any(|r| r.ref_kind == X86SymbolRefKind::TemplateSpecialization);
}
#[test]
fn test_is_declaration_line() {
let fs = X86ToolingFindSymbol::new();
assert!(fs.is_declaration_line("void foo()", "foo"));
assert!(fs.is_declaration_line("int bar(int x, int y);", "bar"));
assert!(!fs.is_declaration_line("// void foo()", "foo"));
assert!(!fs.is_declaration_line(" foo();", "foo"));
}
#[test]
fn test_code_quality_high_cyclomatic_complexity() {
let mut cq = X86ToolingCodeQuality::new();
let path = temp_file(
"test_high_cc.c",
"int func(int a, int b, int c) {\n\
if (a > 1) {\n\
if (b > 2) {\n\
if (c > 3) {\n\
for(int i=0;i<10;i++) {\n\
if(i%2==0) continue;\n\
while(a>0) { a--; }\n\
}\n\
}\n\
}\n\
}\n\
switch(b) { case 1: break; case 2: break; }\n\
return a && b || c;\n\
}",
);
let result = cq.analyze_file(&path);
assert!(result.success);
assert!(!result.diagnostics.is_empty());
fs::remove_file(&path).ok();
}
#[test]
fn test_code_quality_dead_code_detection() {
let mut cq = X86ToolingCodeQuality::new();
let path = temp_file(
"test_dead2.c",
"int used() { return 1; }\n\
int not_used() { return 0; }\n\
int main() { return used(); }",
);
let result = cq.analyze_file(&path);
assert!(result.success);
fs::remove_file(&path).ok();
}
#[test]
fn test_code_quality_unused_parameter() {
let mut cq = X86ToolingCodeQuality::new();
let path = temp_file(
"test_unused_param.c",
"int func(int used_param, int unused_param) {\n\
return used_param * 2;\n\
}",
);
let result = cq.analyze_file(&path);
assert!(result.success);
fs::remove_file(&path).ok();
}
#[test]
fn test_code_quality_unreachable_code() {
let mut cq = X86ToolingCodeQuality::new();
let path = temp_file(
"test_unreachable.c",
"int func() {\n\
return 0;\n\
int x = 5;\n\
return x;\n\
}",
);
let result = cq.analyze_file(&path);
assert!(result.success);
fs::remove_file(&path).ok();
}
#[test]
fn test_duplication_detection_exact_copy() {
let mut cq = X86ToolingCodeQuality::new();
let path = temp_file(
"test_exact_dup.c",
"int a() { int x = 1; x++; x = x * 2; return x; }\n\
int b() { int x = 1; x++; x = x * 2; return x; }",
);
cq.duplication_threshold = 0.1;
let result = cq.analyze_file(&path);
assert!(result.success);
fs::remove_file(&path).ok();
}
#[test]
fn test_code_quality_complexity_metrics_nesting() {
let source = vec![
"void nested() {",
" while (1) {",
" for (;;) {",
" if (x) {",
" doSomething();",
" }",
" }",
" }",
"}",
];
let mut cm = X86ComplexityMetrics::new("nested".into());
cm.compute_cyclomatic(&source);
cm.compute_cognitive(&source);
assert!(cm.nesting_depth > 0 || cm.cognitive_complexity > 0);
}
#[test]
fn test_executor_standalone_multiple_actions() {
let mut exec = X86ToolExecutor::new(X86ToolExecutionMode::Standalone);
exec.add_action(X86FrontendAction::new(
X86FrontendActionKind::ASTBuild,
PathBuf::from("noexist1.c"),
));
exec.add_action(X86FrontendAction::new(
X86FrontendActionKind::EmitLLVM,
PathBuf::from("noexist2.c"),
));
let results = exec.execute();
assert_eq!(results.results.len(), 2);
}
#[test]
fn test_executor_parallel_multiple() {
let mut exec = X86ToolExecutor::new(X86ToolExecutionMode::Parallel);
for i in 0..5 {
exec.add_action(X86FrontendAction::new(
X86FrontendActionKind::SyntaxOnly,
PathBuf::from(format!("file{}.c", i)),
));
}
let results = exec.execute();
assert_eq!(results.results.len(), 5);
}
#[test]
fn test_tool_results_to_json_format() {
let mut results = X86ToolResults::new(X86ToolExecutionMode::MapReduce);
results.aggregated.files_processed = 3;
results.aggregated.files_modified = 2;
results.aggregated.elapsed_ms = 150;
let json = results.to_json();
assert!(json.starts_with('{'));
assert!(json.ends_with('}'));
assert!(json.contains("MapReduce"));
assert!(json.contains("150"));
}
#[test]
fn test_frontend_action_with_args() {
let action =
X86FrontendAction::new(X86FrontendActionKind::EmitAssembly, PathBuf::from("test.c"))
.with_args(vec!["-O2".into(), "-march=x86-64".into()]);
assert_eq!(action.compiler_args.len(), 2);
assert!(action.compiler_args.contains(&"-O2".into()));
}
#[test]
fn test_tool_executor_verbose() {
let mut exec = X86ToolExecutor::new(X86ToolExecutionMode::Standalone);
exec.verbose = true;
assert!(exec.verbose);
}
#[test]
fn test_symbol_ref_with_range_and_context() {
let mut r = X86SymbolRef::new(
"testFunc".into(),
X86SymbolRefKind::Definition,
PathBuf::from("test.c"),
X86ToolingLocation::new(10, 5),
);
r.range = Some(X86ToolingRange::new(
X86ToolingLocation::new(10, 5),
X86ToolingLocation::new(10, 20),
));
r.context = "void testFunc(int x)".into();
assert!(r.range.is_some());
assert_eq!(r.context, "void testFunc(int x)");
}
#[test]
fn test_tooling_diagnostic_with_fixit() {
let diag = X86ToolingDiagnostic {
location: X86ToolingLocation::new(5, 10),
range: Some(X86ToolingRange::new(
X86ToolingLocation::new(5, 10),
X86ToolingLocation::new(5, 20),
)),
message: "unused variable 'x'".into(),
severity: X86ToolingSeverity::Warning,
file_path: PathBuf::from("test.c"),
fixit_hints: vec![X86ToolingFixIt::new(
X86ToolingRange::new(
X86ToolingLocation::new(5, 1),
X86ToolingLocation::new(5, 12),
),
"".into(),
"remove unused variable".into(),
)],
};
assert_eq!(diag.severity, X86ToolingSeverity::Warning);
assert_eq!(diag.fixit_hints.len(), 1);
}
#[test]
fn test_tooling_result_warning_and_error_counts() {
let mut result = X86ToolingResult::new();
result.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(1, 1),
range: None,
message: "warn1".into(),
severity: X86ToolingSeverity::Warning,
file_path: PathBuf::from("test.c"),
fixit_hints: Vec::new(),
});
result.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(2, 1),
range: None,
message: "warn2".into(),
severity: X86ToolingSeverity::Warning,
file_path: PathBuf::from("test.c"),
fixit_hints: Vec::new(),
});
result.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(3, 1),
range: None,
message: "err1".into(),
severity: X86ToolingSeverity::Error,
file_path: PathBuf::from("test.c"),
fixit_hints: Vec::new(),
});
assert_eq!(result.warning_count(), 2);
assert_eq!(result.error_count(), 1);
}
#[test]
fn test_replacement_apply_single_line() {
let r = X86ToolingReplacement::new(
PathBuf::from("test.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 4)),
"XYZ".into(),
"replace".into(),
);
let source = "abcdef\n";
let result = r.apply(source).unwrap();
assert!(result.contains("XYZ"));
assert!(result.contains("ef"));
}
#[test]
fn test_replacement_apply_first_line() {
let r = X86ToolingReplacement::new(
PathBuf::from("test.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 1)),
"// ".into(),
"prepend comment".into(),
);
let source = "int main() {\n return 0;\n}\n";
let result = r.apply(source).unwrap();
assert!(result.starts_with("// "));
}
#[test]
fn test_replacement_apply_delete_line() {
let r = X86ToolingReplacement::new(
PathBuf::from("test.c"),
X86ToolingRange::new(
X86ToolingLocation::new(2, 1),
X86ToolingLocation::new(2, 20),
),
"".into(),
"delete line".into(),
);
let source = "line1\nline2_to_delete\nline3\n";
let result = r.apply(source).unwrap();
assert!(!result.contains("line2_to_delete"));
assert!(result.contains("line1"));
assert!(result.contains("line3"));
}
#[test]
fn test_parse_json_string_escaped() {
let db = X86CompilationDatabase::new();
let result = db.extract_json_string(r#"{"key": "hello\"world"}"#, "key");
assert!(result.is_some());
}
#[test]
fn test_parse_json_string_array() {
let db = X86CompilationDatabase::new();
let result = db
.extract_json_string_array(r#"{"args": ["-I/usr/include", "-DDEBUG", "-O2"]}"#, "args");
assert!(result.is_some());
let args = result.unwrap();
assert_eq!(args.len(), 3);
}
#[test]
fn test_parse_json_string_array_empty() {
let db = X86CompilationDatabase::new();
let result = db.extract_json_string_array(r#"{"args": []}"#, "args");
assert!(result.is_none());
}
#[test]
fn test_parse_json_string_missing_key() {
let db = X86CompilationDatabase::new();
let result = db.extract_json_string(r#"{"other": "value"}"#, "key");
assert!(result.is_none());
}
#[test]
fn test_tooling_full_sub_phases() {
let mut tf = X86ToolingFull::new();
let path = temp_file("test_phase.c", "int main() { return 0; }");
tf.add_file(path.clone());
let ast_results = tf.run_ast_matching();
let refactor_result = tf.run_refactoring();
let quality_results = tf.run_code_quality();
assert!(!ast_results.is_empty());
assert!(refactor_result.success);
assert!(!quality_results.is_empty());
fs::remove_file(&path).ok();
}
#[test]
fn test_tooling_full_with_verbose_v2() {
let mut tf = X86ToolingFull::new().with_verbose(true);
let path = temp_file("test_verbose.c", "int main() { return 0; }");
tf.add_file(path.clone());
let _results = tf.run_all();
assert!(tf.verbose);
assert!(tf.lib_tooling.verbose);
assert!(tf.ast_matchers.verbose);
assert!(tf.refactoring_tool.verbose);
assert!(tf.find_symbol.verbose);
assert!(tf.code_quality.verbose);
fs::remove_file(&path).ok();
}
#[test]
fn test_lib_tooling_get_compilation_db() {
let lt = X86LibTooling::new();
let db = lt.get_compilation_db();
assert!(db.is_empty());
}
#[test]
fn test_lib_tooling_create_action_with_db() {
let mut lt = X86LibTooling::new();
let mut db = X86CompilationDatabase::new();
let mut entry =
X86CompilationDBEntry::new(PathBuf::from("/src"), PathBuf::from("main.cpp"));
entry.arguments = vec![
"clang++".into(),
"-std=c++17".into(),
"-c".into(),
"main.cpp".into(),
];
db.entries.push(entry);
lt.compilation_db = db;
let action = lt.create_syntax_action(Path::new("main.cpp"));
assert!(!action.compiler_args.is_empty());
assert!(action.compiler_args.contains(&"-std=c++17".into()));
}
#[test]
fn test_refactoring_tool_header_source_coordination() {
let mut rt = X86RefactoringTool::new();
rt.coordinate_header_source(Path::new("module.h"), Path::new("module.c"))
.unwrap();
assert_eq!(rt.header_source_map.len(), 2);
assert_eq!(
rt.header_source_map.get(&PathBuf::from("module.h")),
Some(&PathBuf::from("module.c"))
);
assert_eq!(
rt.header_source_map.get(&PathBuf::from("module.c")),
Some(&PathBuf::from("module.h"))
);
}
#[test]
fn test_generate_report_with_dead_code() {
let mut cq = X86ToolingCodeQuality::new();
let path = temp_file("test_report_dc.c", "int unused() { return 0; }");
cq.analyze_file(&path);
let report = cq.generate_report(&path);
assert!(report.contains("Dead Code"));
fs::remove_file(&path).ok();
}
#[test]
fn test_generate_report_with_duplication() {
let mut cq = X86ToolingCodeQuality::new();
let path = temp_file(
"test_report_dup.c",
"int a() { int x=1; x++; return x; }\nint b() { int x=1; x++; return x; }",
);
cq.duplication_threshold = 0.1;
cq.analyze_file(&path);
let report = cq.generate_report(&path);
assert!(report.contains("Duplication"));
fs::remove_file(&path).ok();
}
#[test]
fn test_many_edits_in_refactoring_tool() {
let mut rt = X86RefactoringTool::new();
for i in 0..100 {
rt.insert(
Path::new(&format!("file{}.c", i / 10)),
X86ToolingLocation::new(i + 1, 1),
"// edit\n",
&format!("edit {}", i),
);
}
assert_eq!(rt.edit_count(), 100);
assert_eq!(rt.fixit_hints.len(), 100);
}
#[test]
fn test_many_matchers() {
let mut matchers = X86ASTMatchers::new();
for i in 0..50 {
matchers.add_matcher(X86ASTMatchers::has_name(&format!("func{}", i)));
}
assert_eq!(matchers.matchers.len(), 50);
}
#[test]
fn test_many_compilation_db_entries() {
let mut db = X86CompilationDatabase::new();
for i in 0..200 {
db.entries.push(X86CompilationDBEntry::new(
PathBuf::from("/src"),
PathBuf::from(format!("file{}.c", i)),
));
}
assert_eq!(db.len(), 200);
}
#[test]
fn test_large_source_complexity_analysis() {
let mut cq = X86ToolingCodeQuality::new();
let mut source = String::from("int main() {\n");
for i in 0..100 {
source.push_str(&format!(" int x{} = {};\n", i, i));
}
source.push_str(" return 0;\n}");
let path = temp_file("test_large.c", &source);
let result = cq.analyze_file(&path);
assert!(result.success);
fs::remove_file(&path).ok();
}
#[test]
fn test_optionally_combinator() {
let m = X86ASTMatchers::optionally(X86ASTMatchers::is_const());
assert!(m.to_query_string().contains("optionally"));
}
#[test]
fn test_has_combinator() {
let m = X86ASTMatchers::has(X86ASTMatchers::return_stmt());
assert!(m.to_query_string().contains("has("));
}
#[test]
fn test_nested_combinators() {
let m = X86ASTMatchers::all_of(vec![
X86ASTMatchers::function_decl(),
X86ASTMatchers::any_of(vec![
X86ASTMatchers::is_static(),
X86ASTMatchers::is_inline(),
]),
]);
let qs = m.to_query_string();
assert!(qs.contains("allOf"));
assert!(qs.contains("anyOf"));
assert!(qs.contains("functionDecl"));
assert!(qs.contains("isStatic"));
assert!(qs.contains("isInline"));
}
#[test]
fn test_range_contains_exact() {
let range = X86ToolingRange::new(
X86ToolingLocation::new(5, 5),
X86ToolingLocation::new(5, 10),
);
assert!(range.contains(X86ToolingLocation::new(5, 5)));
assert!(range.contains(X86ToolingLocation::new(5, 7)));
assert!(range.contains(X86ToolingLocation::new(5, 10)));
assert!(!range.contains(X86ToolingLocation::new(5, 4)));
assert!(!range.contains(X86ToolingLocation::new(5, 11)));
}
#[test]
fn test_range_overlaps_adjacent() {
let r1 = X86ToolingRange::new(
X86ToolingLocation::new(1, 1),
X86ToolingLocation::new(1, 10),
);
let r2 = X86ToolingRange::new(
X86ToolingLocation::new(1, 10),
X86ToolingLocation::new(1, 20),
);
assert!(!r1.overlaps(&r2));
}
#[test]
fn test_range_overlaps_same() {
let r1 = X86ToolingRange::new(
X86ToolingLocation::new(1, 1),
X86ToolingLocation::new(5, 10),
);
assert!(r1.overlaps(&r1));
}
#[test]
fn test_range_display_format() {
let range = X86ToolingRange::new(
X86ToolingLocation::new(3, 7),
X86ToolingLocation::new(3, 15),
);
let s = format!("{}", range);
assert!(s.contains("3:7"));
assert!(s.contains("3:15"));
}
#[test]
fn test_all_node_kinds_display_non_empty() {
let all_kinds = [
X86MatchedNodeKind::FunctionDecl,
X86MatchedNodeKind::VarDecl,
X86MatchedNodeKind::RecordDecl,
X86MatchedNodeKind::EnumDecl,
X86MatchedNodeKind::CXXRecordDecl,
X86MatchedNodeKind::ClassTemplateDecl,
X86MatchedNodeKind::NamespaceDecl,
X86MatchedNodeKind::UsingDecl,
X86MatchedNodeKind::TypedefDecl,
X86MatchedNodeKind::FieldDecl,
X86MatchedNodeKind::ParmVarDecl,
X86MatchedNodeKind::CXXMethodDecl,
X86MatchedNodeKind::CXXConstructorDecl,
X86MatchedNodeKind::CXXDestructorDecl,
X86MatchedNodeKind::FunctionTemplateDecl,
X86MatchedNodeKind::ClassTemplateSpecializationDecl,
X86MatchedNodeKind::CallExpr,
X86MatchedNodeKind::CXXMemberCallExpr,
X86MatchedNodeKind::CXXOperatorCallExpr,
X86MatchedNodeKind::DeclRefExpr,
X86MatchedNodeKind::MemberExpr,
X86MatchedNodeKind::IntegerLiteral,
X86MatchedNodeKind::FloatLiteral,
X86MatchedNodeKind::CharacterLiteral,
X86MatchedNodeKind::StringLiteral,
X86MatchedNodeKind::BooleanLiteral,
X86MatchedNodeKind::NullPtrLiteral,
X86MatchedNodeKind::UnaryOperator,
X86MatchedNodeKind::BinaryOperator,
X86MatchedNodeKind::ConditionalOperator,
X86MatchedNodeKind::CastExpr,
X86MatchedNodeKind::ExplicitCastExpr,
X86MatchedNodeKind::CStyleCastExpr,
X86MatchedNodeKind::StaticCastExpr,
X86MatchedNodeKind::DynamicCastExpr,
X86MatchedNodeKind::ConstCastExpr,
X86MatchedNodeKind::ReinterpretCastExpr,
X86MatchedNodeKind::ImplicitCastExpr,
X86MatchedNodeKind::MaterializeTemporaryExpr,
X86MatchedNodeKind::ArraySubscriptExpr,
X86MatchedNodeKind::InitListExpr,
X86MatchedNodeKind::ParenListExpr,
X86MatchedNodeKind::LambdaExpr,
X86MatchedNodeKind::NewExpr,
X86MatchedNodeKind::DeleteExpr,
X86MatchedNodeKind::ThisExpr,
X86MatchedNodeKind::DefaultArgExpr,
X86MatchedNodeKind::CXXConstructExpr,
X86MatchedNodeKind::CXXTemporaryObjectExpr,
X86MatchedNodeKind::CXXBindTemporaryExpr,
X86MatchedNodeKind::CXXFunctionalCastExpr,
X86MatchedNodeKind::IfStmt,
X86MatchedNodeKind::ForStmt,
X86MatchedNodeKind::WhileStmt,
X86MatchedNodeKind::DoStmt,
X86MatchedNodeKind::SwitchStmt,
X86MatchedNodeKind::ReturnStmt,
X86MatchedNodeKind::BreakStmt,
X86MatchedNodeKind::ContinueStmt,
X86MatchedNodeKind::GotoStmt,
X86MatchedNodeKind::CompoundStmt,
X86MatchedNodeKind::DeclStmt,
X86MatchedNodeKind::NullStmt,
X86MatchedNodeKind::CaseStmt,
X86MatchedNodeKind::DefaultStmt,
X86MatchedNodeKind::LabelStmt,
X86MatchedNodeKind::CXXTryStmt,
X86MatchedNodeKind::CXXCatchStmt,
X86MatchedNodeKind::CXXThrowExpr,
X86MatchedNodeKind::CXXForRangeStmt,
X86MatchedNodeKind::PointerType,
X86MatchedNodeKind::ReferenceType,
X86MatchedNodeKind::ArrayType,
X86MatchedNodeKind::ElaboratedType,
X86MatchedNodeKind::TypedefType,
X86MatchedNodeKind::TemplateSpecializationType,
X86MatchedNodeKind::AutoType,
X86MatchedNodeKind::DecayedType,
X86MatchedNodeKind::ParenType,
X86MatchedNodeKind::AttributedType,
X86MatchedNodeKind::Any,
];
for kind in &all_kinds {
let s = format!("{}", kind);
assert!(!s.is_empty(), "Empty display for {:?}", kind);
}
}
#[test]
fn test_all_predicate_to_query_string() {
let predicates = [
X86ASTMatcherPredicate::IsExplicit,
X86ASTMatcherPredicate::IsOverride,
X86ASTMatcherPredicate::IsFinal,
X86ASTMatcherPredicate::IsVolatile,
X86ASTMatcherPredicate::IsConst,
X86ASTMatcherPredicate::IsStatic,
X86ASTMatcherPredicate::IsInline,
X86ASTMatcherPredicate::IsPublic,
X86ASTMatcherPredicate::IsProtected,
X86ASTMatcherPredicate::IsPrivate,
X86ASTMatcherPredicate::IsExpandedFromMacro,
X86ASTMatcherPredicate::IsImplicit,
X86ASTMatcherPredicate::IsDefaulted,
X86ASTMatcherPredicate::IsDeleted,
X86ASTMatcherPredicate::IsNoThrow,
X86ASTMatcherPredicate::IsConstexpr,
X86ASTMatcherPredicate::IsMutable,
X86ASTMatcherPredicate::IsFloating,
X86ASTMatcherPredicate::IsPointer,
X86ASTMatcherPredicate::IsReference,
X86ASTMatcherPredicate::IsConstQualified,
X86ASTMatcherPredicate::IsVolatileQualified,
X86ASTMatcherPredicate::Anything,
];
for pred in &predicates {
let s = pred.to_query_string();
assert!(!s.is_empty());
assert!(s.contains('('));
assert!(s.contains(')'));
}
}
#[test]
fn test_find_and_rename_pattern() {
let source = "void oldName() { oldName(); }\nint main() { oldName(); }";
let file = Path::new("test.c");
let mut fs = X86ToolingFindSymbol::new();
fs.set_symbol("oldName");
let refs = fs.find_references_in_source(source, "oldName", file);
assert!(refs.len() >= 2);
let mut rt = X86RefactoringTool::new();
for r in &refs {
if let Some(range) = r.range {
rt.replace(file, range, "newName", "rename");
}
}
assert!(rt.edit_count() > 0);
}
#[test]
fn test_halstead_metrics_nonzero() {
let source = vec![
"int calculate(int a, int b) {",
" int c = a + b;",
" int d = c * 2;",
" return d / (a - b);",
"}",
];
let mut cm = X86ComplexityMetrics::new("calculate".into());
cm.compute_halstead(&source);
assert!(cm.halstead_volume >= 0.0);
}
#[test]
fn test_tool_results_aggregate_with_errors() {
let mut results = X86ToolResults::new(X86ToolExecutionMode::Parallel);
let mut r1 = X86ToolingResult::new();
r1.success = false;
r1.files_processed = 2;
r1.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(1, 1),
range: None,
message: "error".into(),
severity: X86ToolingSeverity::Error,
file_path: PathBuf::from("test.c"),
fixit_hints: Vec::new(),
});
let r2 = X86ToolingResult::new();
results.results = vec![r1, r2];
results.aggregate();
assert!(!results.aggregated.success);
assert_eq!(results.aggregated.files_processed, 2);
assert_eq!(results.aggregated.error_count(), 1);
}
#[test]
fn test_lib_tooling_run_with_actions() {
let mut lt = X86LibTooling::new();
lt.add_frontend_action(X86FrontendAction::new(
X86FrontendActionKind::ASTBuild,
PathBuf::from("noexist.c"),
));
lt.add_frontend_action(X86FrontendAction::new(
X86FrontendActionKind::SyntaxOnly,
PathBuf::from("noexist2.c"),
));
let results = lt.run();
assert!(!results.results.is_empty());
assert_eq!(results.results.len(), 2);
}
#[test]
fn test_lib_tooling_create_plugin_action_with_db_args() {
let mut lt = X86LibTooling::new();
let mut db = X86CompilationDatabase::new();
let mut entry =
X86CompilationDBEntry::new(PathBuf::from("/src"), PathBuf::from("plugin_test.cpp"));
entry.arguments = vec!["clang++".into(), "-std=c++20".into(), "-c".into()];
db.entries.push(entry);
lt.compilation_db = db;
let action = lt.create_plugin_action("my-checker", Path::new("plugin_test.cpp"));
assert!(!action.compiler_args.is_empty());
}
#[test]
fn test_ast_match_with_all_bindings() {
let m = X86ASTMatch::new(
X86MatchedNodeKind::CallExpr,
X86ToolingLocation::new(1, 1),
PathBuf::from("test.cpp"),
)
.with_binding("callee", "foo")
.with_binding("arg0", "42")
.with_binding("arg1", "3.14")
.with_binding("qualifier", "std");
assert_eq!(m.bindings.len(), 4);
let display = format!("{}", m);
assert!(display.contains("callee=foo"));
}
#[test]
fn test_match_kind_differences() {
let direct = X86MatchKind::Direct;
let descent = X86MatchKind::Descent;
let ascent = X86MatchKind::Ascent;
assert_ne!(direct, descent);
assert_ne!(descent, ascent);
assert_ne!(ascent, direct);
}
#[test]
fn test_frontend_action_builder_pattern() {
let action =
X86FrontendAction::new(X86FrontendActionKind::EmitObj, PathBuf::from("main.c"))
.with_args(vec!["-O2".into(), "-g".into(), "-Wall".into()]);
assert_eq!(action.kind, X86FrontendActionKind::EmitObj);
assert_eq!(action.compiler_args.len(), 3);
}
#[test]
fn test_tool_executor_builder_pattern() {
let db = X86CompilationDatabase::new();
let exec = X86ToolExecutor::new(X86ToolExecutionMode::Parallel).with_db(db);
assert!(exec.compilation_db.is_empty());
}
#[test]
fn test_tooling_full_builder_pattern() {
let tf = X86ToolingFull::new()
.with_working_dir(PathBuf::from("/project"))
.with_verbose(true)
.with_compilation_db(PathBuf::from("build/compile_commands.json"));
assert_eq!(tf.working_dir, PathBuf::from("/project"));
assert!(tf.verbose);
assert!(tf.compilation_db_path.is_some());
}
#[test]
fn test_tooling_result_zero_counts() {
let result = X86ToolingResult::new();
assert_eq!(result.warning_count(), 0);
assert_eq!(result.error_count(), 0);
assert!(!result.has_errors());
assert!(result.success);
}
#[test]
fn test_tooling_result_mixed_severities() {
let mut result = X86ToolingResult::new();
result.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(1, 1),
range: None,
message: "fatal error".into(),
severity: X86ToolingSeverity::Fatal,
file_path: PathBuf::from("test.c"),
fixit_hints: Vec::new(),
});
assert!(result.has_errors());
assert_eq!(result.error_count(), 1);
}
#[test]
fn test_compilation_db_parse_complex_json() {
let json = r#"[
{
"directory": "/home/project",
"file": "src/main.cpp",
"command": "clang++ -std=c++17 -I./include -I/opt/boost/include -DFOO=bar -c src/main.cpp -o build/main.o",
"arguments": ["clang++", "-std=c++17", "-I./include", "-I/opt/boost/include", "-DFOO=bar", "-c", "src/main.cpp", "-o", "build/main.o"]
},
{
"directory": "/home/project",
"file": "src/utils.cpp",
"arguments": ["clang++", "-std=c++17", "-I./include", "-c", "src/utils.cpp"]
}
]"#;
let db = X86CompilationDatabase::new();
let entries = db.parse_json_compile_commands(json);
assert_eq!(entries.len(), 2);
assert_eq!(entries[0].file, PathBuf::from("src/main.cpp"));
assert_eq!(entries[1].file, PathBuf::from("src/utils.cpp"));
}
#[test]
fn test_compilation_db_parse_non_json() {
let db = X86CompilationDatabase::new();
let entries = db.parse_json_compile_commands("not json at all");
assert!(entries.is_empty());
}
#[test]
fn test_compilation_db_parse_empty_array() {
let db = X86CompilationDatabase::new();
let entries = db.parse_json_compile_commands("[]");
assert!(entries.is_empty());
}
#[test]
fn test_compilation_db_lookup_by_filename() {
let mut db = X86CompilationDatabase::new();
db.entries.push(X86CompilationDBEntry::new(
PathBuf::from("/work"),
PathBuf::from("/work/src/foo.c"),
));
let result = db.lookup(Path::new("/work/src/foo.c"));
assert!(result.is_some());
}
#[test]
fn test_compilation_db_lookup_by_basename() {
let mut db = X86CompilationDatabase::new();
db.entries.push(X86CompilationDBEntry::new(
PathBuf::from("/any"),
PathBuf::from("bar.c"),
));
let result = db.lookup(Path::new("bar.c"));
assert!(result.is_some());
}
#[test]
fn test_match_on_large_source() {
let matchers = X86ASTMatchers::new();
let mut source = String::new();
for i in 0..200 {
source.push_str(&format!("void func{}() {{ return; }}\n", i));
}
let file = Path::new("large.c");
let predicate = X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::FunctionDecl);
let matches = matchers.match_in_source(&source, &predicate, file);
assert!(!matches.is_empty());
}
#[test]
fn test_run_on_file_with_empty_file() {
let path = temp_file("test_empty.c", "");
let mut matchers = X86ASTMatchers::new();
matchers.add_matcher(X86ASTMatchers::function_decl());
let result = matchers.run_on_file(&path);
assert!(result.success);
fs::remove_file(&path).ok();
}
#[test]
fn test_run_on_file_with_comment_only() {
let path = temp_file(
"test_comments.c",
"// This is a comment\n/* block comment */",
);
let mut matchers = X86ASTMatchers::new();
matchers.add_matcher(X86ASTMatchers::function_decl());
let result = matchers.run_on_file(&path);
assert!(result.success);
fs::remove_file(&path).ok();
}
#[test]
fn test_find_symbol_partial_word_boundaries() {
let fs = X86ToolingFindSymbol::new();
let source = "int myVar = 5; int myVarLong = 10;";
let file = Path::new("test.c");
let refs = fs.find_references_in_source(source, "myVar", file);
for r in &refs {
if let Some(range) = r.range {
let idx = range.start.column - 1;
let before = if idx > 0 {
source.as_bytes()[idx - 1] as char
} else {
' '
};
let after = if idx + "myVar".len() < source.len() {
source.as_bytes()[idx + "myVar".len()] as char
} else {
' '
};
}
}
}
#[test]
fn test_find_symbol_with_macros() {
let fs = X86ToolingFindSymbol::new();
let source = "#define FOO(x) do_foo(x)\nvoid do_foo(int x) { }\nint main() { FOO(1); }";
let file = Path::new("test.c");
let refs = fs.find_references_in_source(source, "do_foo", file);
assert!(!refs.is_empty());
}
#[test]
fn test_find_symbol_declarations_in_headers() {
let fs = X86ToolingFindSymbol::new();
let source = "#ifndef _H\n#define _H\nextern int global_counter;\n#endif";
let file = Path::new("test.h");
let decls = fs.find_declarations_in_source(source, "global_counter", file);
assert!(!decls.is_empty());
}
#[test]
fn test_code_quality_detect_dead_assignment() {
let mut cq = X86ToolingCodeQuality::new();
let path = temp_file(
"test_dead_assign.c",
"int func() {\n int x = 5;\n x = 10;\n return 0;\n}",
);
let result = cq.analyze_file(&path);
assert!(result.success);
fs::remove_file(&path).ok();
}
#[test]
fn test_code_quality_custom_threshold() {
let mut cq = X86ToolingCodeQuality::new();
cq.duplication_threshold = 0.95;
assert_eq!(cq.duplication_threshold, 0.95);
}
#[test]
fn test_complexity_metrics_combined() {
let source = vec![
"int process(int x, int y, int z) {",
" if (x > 0 && y > 0) {",
" for (int i = 0; i < z; i++) {",
" if (i % 2 == 0) {",
" x += i;",
" } else {",
" y -= i;",
" }",
" }",
" }",
" switch (z) {",
" case 1: return x;",
" case 2: return y;",
" default: return x + y;",
" }",
"}",
];
let mut cm = X86ComplexityMetrics::new("process".into());
cm.compute_cyclomatic(&source);
cm.compute_cognitive(&source);
cm.compute_halstead(&source);
assert!(cm.cyclomatic_complexity >= 5);
assert!(cm.cognitive_complexity >= 0);
assert!(cm.line_count > 0);
}
#[test]
fn test_full_workflow_compile_to_refactor() {
let path = temp_file(
"test_workflow.c",
"#include <stdio.h>\n\nint legacy_func(int a, int b) {\n int result = a + b;\n return result;\n}\n\nint main() {\n int x = legacy_func(1, 2);\n printf(\"%d\\n\", x);\n return 0;\n}",
);
let mut tf = X86ToolingFull::new();
tf.add_file(path.clone());
tf.find_symbol.set_symbol("legacy_func");
let _find_result = tf.find_symbol.run_on_file(&path);
let _quality_result = tf.code_quality.analyze_file(&path);
tf.ast_matchers.add_matcher(X86ASTMatchers::function_decl());
let _ast_result = tf.ast_matchers.run_on_file(&path);
if let Some(ref range) = tf
.find_symbol
.get_references()
.first()
.and_then(|r| r.range)
{
tf.refactoring_tool
.replace(&path, *range, "new_func", "rename legacy_func");
}
let _refactor_result = tf.refactoring_tool.execute_all();
fs::remove_file(&path).ok();
}
#[test]
fn test_compilation_db_to_executor_flow() {
let path = temp_file(
"compile_flow.json",
r#"[
{"directory": "/app", "file": "main.cpp", "arguments": ["clang++", "-std=c++17", "-I./inc", "-c", "main.cpp"]}
]"#,
);
let mut lt = X86LibTooling::new();
let load_result = lt.load_compilation_database(&path);
assert!(load_result.success);
lt.set_execution_mode(X86ToolExecutionMode::Standalone);
let action = lt.create_syntax_action(Path::new("main.cpp"));
let run_result = lt.run_action(action);
assert!(!run_result.success);
fs::remove_file(&path).ok();
}
#[test]
fn test_refactoring_then_quality_flow() {
let path = temp_file(
"test_ref_qual_flow.c",
"int bad_func() { int x; x = 1; x = 2; return x; }\nint main() { return bad_func(); }",
);
let mut cq = X86ToolingCodeQuality::new();
let _ = cq.analyze_file(&path);
let mut rt = X86RefactoringTool::new();
rt.insert(
&path,
X86ToolingLocation::new(1, 1),
"// Refactored\n",
"add note",
);
let _ = rt.execute_all();
fs::remove_file(&path).ok();
}
#[test]
fn test_parallel_tool_execution() {
let mut exec = X86ToolExecutor::new(X86ToolExecutionMode::Parallel);
for i in 0..10 {
exec.add_action(X86FrontendAction::new(
X86FrontendActionKind::SyntaxOnly,
PathBuf::from(format!("concurrent{}.c", i)),
));
}
let results = exec.execute();
assert_eq!(results.results.len(), 10);
}
#[test]
fn test_map_reduce_aggregation() {
let mut exec = X86ToolExecutor::new(X86ToolExecutionMode::MapReduce);
for i in 0..5 {
exec.add_action(X86FrontendAction::new(
X86FrontendActionKind::ASTBuild,
PathBuf::from(format!("chunk{}.c", i)),
));
}
let results = exec.execute();
results.aggregate();
assert_eq!(results.aggregated.files_processed, 5);
}
#[test]
fn test_all_matcher_query_strings_end_with_paren() {
let all = vec![
X86ASTMatchers::function_decl(),
X86ASTMatchers::var_decl(),
X86ASTMatchers::is_definition(),
X86ASTMatchers::is_virtual(),
X86ASTMatchers::is_const(),
X86ASTMatchers::has_name("foo"),
X86ASTMatchers::call_expr(),
X86ASTMatchers::if_stmt(),
X86ASTMatchers::return_stmt(),
X86ASTMatchers::integer_literal(),
X86ASTMatchers::pointer_type(),
X86ASTMatchers::anything(),
];
for m in &all {
let qs = m.to_query_string();
if matches!(m, X86ASTMatcherPredicate::NodeKind(_)) {
continue;
}
}
}
#[test]
fn test_deeply_nested_combinator() {
let inner = X86ASTMatchers::all_of(vec![
X86ASTMatchers::has_name("foo"),
X86ASTMatchers::is_static(),
X86ASTMatchers::is_inline(),
]);
let middle = X86ASTMatchers::any_of(vec![
X86ASTMatchers::function_decl(),
X86ASTMatchers::cxx_method_decl(),
inner,
]);
let outer = X86ASTMatchers::unless(middle);
let qs = outer.to_query_string();
assert!(qs.contains("unless"));
assert!(qs.contains("anyOf"));
assert!(qs.contains("allOf"));
assert!(qs.contains("hasName"));
assert!(qs.contains("isStatic"));
assert!(qs.contains("isInline"));
}
#[test]
fn test_tool_results_json_valid_structure() {
let results = X86ToolResults::new(X86ToolExecutionMode::Standalone);
let json = results.to_json();
assert!(json.contains("\"mode\""));
assert!(json.contains("\"total_operations\""));
assert!(json.contains("\"total_errors\""));
assert!(json.contains("\"total_warnings\""));
assert!(json.contains("\"files_processed\""));
assert!(json.contains("\"files_modified\""));
assert!(json.contains("\"elapsed_ms\""));
}
#[test]
fn test_replacement_conflict_overlapping() {
let r1 = X86ToolingReplacement::new(
PathBuf::from("same.c"),
X86ToolingRange::new(
X86ToolingLocation::new(5, 1),
X86ToolingLocation::new(5, 20),
),
"A".into(),
"edit A".into(),
);
let r2 = X86ToolingReplacement::new(
PathBuf::from("same.c"),
X86ToolingRange::new(
X86ToolingLocation::new(5, 10),
X86ToolingLocation::new(5, 30),
),
"B".into(),
"edit B".into(),
);
assert!(r1.conflicts_with(&r2));
}
#[test]
fn test_replacement_no_conflict_non_overlapping() {
let r1 = X86ToolingReplacement::new(
PathBuf::from("same.c"),
X86ToolingRange::new(
X86ToolingLocation::new(1, 1),
X86ToolingLocation::new(1, 10),
),
"A".into(),
"edit A".into(),
);
let r2 = X86ToolingReplacement::new(
PathBuf::from("same.c"),
X86ToolingRange::new(
X86ToolingLocation::new(3, 1),
X86ToolingLocation::new(3, 10),
),
"B".into(),
"edit B".into(),
);
assert!(!r1.conflicts_with(&r2));
}
#[test]
fn test_type_matcher_relevance() {
let matchers = X86ASTMatchers::new();
let source = "int* ptr = NULL;\nfloat x = 3.14f;\nconst int MAX = 100;";
let file = Path::new("types.c");
let int_matches =
matchers.match_in_source(source, &X86ASTMatcherPredicate::IsInteger, file);
assert!(!int_matches.is_empty());
let ptr_matches = matchers.match_in_source(
source,
&X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::PointerType),
file,
);
}
#[test]
fn test_match_all_loop_types() {
let matchers = X86ASTMatchers::new();
let source = "for (;;) { break; }\nwhile (1) { continue; }\ndo { } while (0);";
let file = Path::new("loops.c");
let for_matches = matchers.match_in_source(
source,
&X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ForStmt),
file,
);
let while_matches = matchers.match_in_source(
source,
&X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::WhileStmt),
file,
);
let do_matches = matchers.match_in_source(
source,
&X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::DoStmt),
file,
);
assert!(!for_matches.is_empty());
assert!(!while_matches.is_empty());
assert!(!do_matches.is_empty());
}
#[test]
fn test_match_try_catch_throw() {
let matchers = X86ASTMatchers::new();
let source =
"try { throw std::runtime_error(\"err\"); } catch (const std::exception& e) { }";
let file = Path::new("except.cpp");
let try_matches = matchers.match_in_source(
source,
&X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXTryStmt),
file,
);
let catch_matches = matchers.match_in_source(
source,
&X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::CXXCatchStmt),
file,
);
assert!(!try_matches.is_empty());
assert!(!catch_matches.is_empty());
}
#[test]
fn test_symbol_ref_new_with_all_fields() {
let mut r = X86SymbolRef::new(
"mySymbol".into(),
X86SymbolRefKind::FriendDecl,
PathBuf::from("header.h"),
X86ToolingLocation::new(42, 7),
);
r.range = Some(X86ToolingRange::new(
X86ToolingLocation::new(42, 1),
X86ToolingLocation::new(42, 30),
));
r.context = "friend class MyClass;".into();
assert_eq!(r.symbol_name, "mySymbol");
assert_eq!(r.ref_kind, X86SymbolRefKind::FriendDecl);
assert_eq!(r.file_path, PathBuf::from("header.h"));
assert_eq!(r.location.line, 42);
assert_eq!(r.location.column, 7);
assert_eq!(r.context, "friend class MyClass;");
}
#[test]
fn test_extract_json_string_with_escape_sequences() {
let db = X86CompilationDatabase::new();
let result = db.extract_json_string(r#"{"path": "C:\\Users\\test\\file.c"}"#, "path");
assert!(result.is_some());
}
#[test]
fn test_extract_json_string_empty() {
let db = X86CompilationDatabase::new();
let result = db.extract_json_string(r#"{"name": ""}"#, "name");
assert_eq!(result, Some(String::new()));
}
#[test]
fn test_ast_matchers_clear_preserves_nothing() {
let mut matchers = X86ASTMatchers::new();
let path = temp_file("test_clear2.c", "int main() { return 0; }");
matchers.add_matcher(X86ASTMatchers::function_decl());
matchers.add_matcher(X86ASTMatchers::return_stmt());
matchers.run_on_file(&path);
matchers.clear();
assert_eq!(matchers.matchers.len(), 0);
assert_eq!(matchers.match_count(), 0);
fs::remove_file(&path).ok();
}
#[test]
fn test_refactoring_tool_save_all_no_modifications() {
let mut rt = X86RefactoringTool::new();
let result = rt.save_all();
assert!(result.is_ok());
assert_eq!(result.unwrap(), 0);
}
#[test]
fn test_refactoring_tool_dry_run_no_write() {
let mut rt = X86RefactoringTool::new();
rt.dry_run = true;
rt.insert(
Path::new("nonexistent2.c"),
X86ToolingLocation::new(1, 1),
"// test\n",
"insert comment",
);
let _ = rt.execute_all();
let result = rt.save_all();
assert!(result.is_ok());
}
#[test]
fn test_class_hierarchy_multi_level() {
let fs = X86ToolingFindSymbol::new();
let source = "class A { };\nclass B : public A { };\nclass C : public B { };";
let file = Path::new("hierarchy.cpp");
let (bases, derived) = fs.find_class_hierarchy_in_source(source, "B", file);
assert!(!bases.is_empty() || !derived.is_empty());
}
#[test]
fn test_ast_match_range_construction() {
let start = X86ToolingLocation::new(1, 5);
let end = X86ToolingLocation::new(1, 15);
let range = X86ToolingRange::new(start, end);
let m = X86ASTMatch::new(
X86MatchedNodeKind::CXXRecordDecl,
start,
PathBuf::from("test.cpp"),
)
.with_range(range);
let r = m.range.unwrap();
assert_eq!(r.start.line, 1);
assert_eq!(r.start.column, 5);
assert_eq!(r.end.line, 1);
assert_eq!(r.end.column, 15);
}
#[test]
fn test_all_tool_execution_modes_distinct() {
let standalone = X86ToolExecutionMode::Standalone;
let mapreduce = X86ToolExecutionMode::MapReduce;
let parallel = X86ToolExecutionMode::Parallel;
assert_ne!(standalone, mapreduce);
assert_ne!(mapreduce, parallel);
assert_ne!(parallel, standalone);
}
#[test]
fn test_all_refactoring_op_kinds_distinct() {
assert_ne!(X86RefactoringOpKind::Insert, X86RefactoringOpKind::Remove);
assert_ne!(X86RefactoringOpKind::Remove, X86RefactoringOpKind::Replace);
assert_ne!(X86RefactoringOpKind::Replace, X86RefactoringOpKind::Insert);
}
#[test]
fn test_all_dead_code_kinds_distinct() {
let kinds = [
X86DeadCodeKind::UnusedFunction,
X86DeadCodeKind::UnusedVariable,
X86DeadCodeKind::UnusedParameter,
X86DeadCodeKind::UnreachableCode,
X86DeadCodeKind::DeadAssignment,
];
for i in 0..kinds.len() {
for j in (i + 1)..kinds.len() {
assert_ne!(kinds[i], kinds[j]);
}
}
}
#[test]
fn test_e2e_detect_find_refactor() {
let path = temp_file(
"test_e2e.c",
"#include <stdio.h>\n\nint calculate_sum(int a, int b) {\n return a + b;\n}\n\nvoid print_hello() {\n printf(\"hello\\n\");\n}\n\nint main() {\n int result = calculate_sum(10, 20);\n print_hello();\n printf(\"Result: %d\\n\", result);\n return 0;\n}",
);
let mut matchers = X86ASTMatchers::new();
matchers.add_matcher(X86ASTMatchers::function_decl());
let _ = matchers.run_on_file(&path);
let mut cq = X86ToolingCodeQuality::new();
let quality_result = cq.analyze_file(&path);
assert!(quality_result.success);
let mut fs = X86ToolingFindSymbol::new();
fs.set_symbol("calculate_sum");
let _ = fs.run_on_file(&path);
assert!(fs.reference_count() > 0);
let mut rt = X86RefactoringTool::new();
for r in fs.get_references() {
if let Some(range) = r.range {
rt.replace(&path, range, "add_numbers", "rename calculate_sum");
}
}
let _ = rt.execute_all();
fs::remove_file(&path).ok();
}
#[test]
fn test_frontend_action_all_kinds_coverage() {
let kinds = vec![
X86FrontendActionKind::SyntaxOnly,
X86FrontendActionKind::ASTBuild,
X86FrontendActionKind::EmitLLVM,
X86FrontendActionKind::EmitAssembly,
X86FrontendActionKind::EmitObj,
X86FrontendActionKind::EmitFull,
X86FrontendActionKind::PluginAction,
X86FrontendActionKind::Custom,
];
for kind in kinds {
let action = X86FrontendAction::new(kind, PathBuf::from("test.c"));
assert_eq!(action.kind, kind);
let display = format!("{}", kind);
assert!(!display.is_empty());
}
}
#[test]
fn test_replacement_apply_mid_line() {
let r = X86ToolingReplacement::new(
PathBuf::from("test.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 5), X86ToolingLocation::new(1, 8)),
"XXX".into(),
"mid-line replacement".into(),
);
let source = "abcd efgh ijkl\n";
let result = r.apply(source).unwrap();
assert!(result.contains("XXX"));
assert!(result.contains("abcd"));
assert!(result.contains("ijkl"));
}
#[test]
fn test_replacement_apply_insert_at_start() {
let r = X86ToolingReplacement::new(
PathBuf::from("test.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 1), X86ToolingLocation::new(1, 1)),
"PREFIX ".into(),
"insert prefix".into(),
);
let source = "content\n";
let result = r.apply(source).unwrap();
assert!(result.starts_with("PREFIX "));
assert!(result.contains("content"));
}
#[test]
fn test_replacement_apply_append_at_end() {
let r = X86ToolingReplacement::new(
PathBuf::from("test.c"),
X86ToolingRange::new(X86ToolingLocation::new(1, 8), X86ToolingLocation::new(1, 8)),
" SUFFIX".into(),
"append suffix".into(),
);
let source = "content\n";
let result = r.apply(source).unwrap();
assert!(result.contains("SUFFIX"));
assert!(result.contains("content"));
}
#[test]
fn test_tooling_full_run_all_with_multiple_files() {
let path1 = temp_file("tf_multi1.c", "int a() { return 1; }");
let path2 = temp_file("tf_multi2.c", "int b() { return 2; }");
let path3 = temp_file("tf_multi3.c", "int c() { return 3; }");
let mut tf = X86ToolingFull::new();
tf.add_file(path1.clone());
tf.add_file(path2.clone());
tf.add_file(path3.clone());
let results = tf.run_all();
assert!(results.len() > 0);
let summary = tf.generate_summary();
assert_eq!(summary.total_files, 3);
fs::remove_file(&path1).ok();
fs::remove_file(&path2).ok();
fs::remove_file(&path3).ok();
}
#[test]
fn test_tooling_full_sub_phases_return_correct_types() {
let path = temp_file("tf_phases.c", "int main() { return 0; }");
let mut tf = X86ToolingFull::new();
tf.add_file(path.clone());
let ast_results = tf.run_ast_matching();
assert_eq!(ast_results.len(), 1);
assert!(ast_results[0].success);
let refactor_result = tf.run_refactoring();
assert!(refactor_result.success);
let quality_results = tf.run_code_quality();
assert_eq!(quality_results.len(), 1);
assert!(quality_results[0].success);
fs::remove_file(&path).ok();
}
#[test]
fn test_ast_matchers_add_many_and_run() {
let path = temp_file("many_matchers.c", "int x; int y; int z; void f() { }");
let mut matchers = X86ASTMatchers::new();
matchers.add_matcher(X86ASTMatchers::function_decl());
matchers.add_matcher(X86ASTMatchers::var_decl());
matchers.add_matcher(X86ASTMatchers::call_expr());
matchers.add_matcher(X86ASTMatchers::if_stmt());
matchers.add_matcher(X86ASTMatchers::while_stmt());
matchers.add_matcher(X86ASTMatchers::for_stmt());
matchers.add_matcher(X86ASTMatchers::return_stmt());
matchers.add_matcher(X86ASTMatchers::switch_stmt());
matchers.add_matcher(X86ASTMatchers::integer_literal());
matchers.add_matcher(X86ASTMatchers::string_literal());
let result = matchers.run_on_file(&path);
assert!(result.success);
assert!(matchers.match_count() > 0);
fs::remove_file(&path).ok();
}
#[test]
fn test_find_symbol_classify_all_reference_types() {
let fs = X86ToolingFindSymbol::new();
assert_eq!(
fs.classify_reference_line("foo(arg1, arg2);", "foo"),
X86SymbolRefKind::Call
);
assert_eq!(
fs.classify_reference_line("void foo(int x);", "foo"),
X86SymbolRefKind::Declaration
);
assert_eq!(
fs.classify_reference_line("int foo() { return 0; }", "foo"),
X86SymbolRefKind::Definition
);
assert_eq!(
fs.classify_reference_line("void foo() override { }", "foo"),
X86SymbolRefKind::Override
);
assert_eq!(
fs.classify_reference_line("friend class Foo;", "Foo"),
X86SymbolRefKind::FriendDecl
);
assert_eq!(
fs.classify_reference_line("template<> struct Traits<int> { };", "Traits"),
X86SymbolRefKind::TemplateSpecialization
);
}
#[test]
fn test_code_quality_dead_code_classification() {
let mut cq = X86ToolingCodeQuality::new();
let path = temp_file(
"test_all_dead.c",
"int used_func() { return 1; }\nint unused_func() { return 0; }\nint main() { return used_func(); }",
);
let result = cq.analyze_file(&path);
assert!(result.success);
let has_unused = cq
.dead_code
.iter()
.any(|dc| dc.name == "unused_func" && dc.kind == X86DeadCodeKind::UnusedFunction);
fs::remove_file(&path).ok();
}
#[test]
fn test_cyclomatic_complexity_simple_function() {
let source = vec!["int simple() {", " return 42;", "}"];
let mut cm = X86ComplexityMetrics::new("simple".into());
cm.compute_cyclomatic(&source);
assert_eq!(cm.cyclomatic_complexity, 1);
}
#[test]
fn test_cyclomatic_complexity_ten_branches() {
let source = vec![
"int complex() {",
" if (a) { do1(); }",
" if (b) { do2(); }",
" if (c) { do3(); }",
" if (d) { do4(); }",
" if (e) { do5(); }",
" switch(x) {",
" case 1: break;",
" case 2: break;",
" case 3: break;",
" }",
" return 0;",
"}",
];
let mut cm = X86ComplexityMetrics::new("complex".into());
cm.compute_cyclomatic(&source);
assert!(cm.cyclomatic_complexity >= 8);
}
#[test]
fn test_cognitive_complexity_nesting_penalty() {
let source = vec![
"void nested() {",
" if (a) {", " if (b) {", " if (c) {", " return;",
" }",
" }",
" }",
"}",
];
let mut cm = X86ComplexityMetrics::new("nested".into());
cm.compute_cognitive(&source);
assert!(cm.cognitive_complexity >= 6);
}
#[test]
fn test_count_parameters_edge_cases() {
let cq = X86ToolingCodeQuality::new();
assert_eq!(cq.count_parameters("void f()"), 0);
assert_eq!(cq.count_parameters("void f(void)"), 0);
assert_eq!(cq.count_parameters("int f(int a)"), 1);
assert_eq!(cq.count_parameters("int f(int a, int b)"), 2);
assert_eq!(cq.count_parameters("void f(int a, float b, double c)"), 3);
assert_eq!(cq.count_parameters("void f(int, float, double)"), 3);
assert_eq!(cq.count_parameters("void f(std::vector<int> v)"), 1);
}
#[test]
fn test_generate_report_empty_file() {
let mut cq = X86ToolingCodeQuality::new();
let path = temp_file("test_empty_report.c", "");
cq.analyze_file(&path);
let report = cq.generate_report(&path);
assert!(report.contains("Code Quality Report"));
fs::remove_file(&path).ok();
}
#[test]
fn test_libtooling_load_and_run_flow() {
let db_path = temp_file(
"flow_db.json",
r#"[
{"directory": "/app", "file": "main.cpp", "command": "clang++ -c main.cpp"}
]"#,
);
let mut lt = X86LibTooling::new();
let load_result = lt.load_compilation_database(&db_path);
assert!(load_result.success);
assert_eq!(lt.compilation_db.len(), 1);
let action = lt.create_syntax_action(Path::new("main.cpp"));
assert_eq!(action.kind, X86FrontendActionKind::SyntaxOnly);
let run_result = lt.run_action(action);
assert!(!run_result.success);
fs::remove_file(&db_path).ok();
}
#[test]
fn test_tool_executor_verbose_output() {
let mut exec = X86ToolExecutor::new(X86ToolExecutionMode::Standalone);
exec.verbose = true;
exec.add_action(X86FrontendAction::new(
X86FrontendActionKind::ASTBuild,
PathBuf::from("nonexistent_verbose.c"),
));
let results = exec.execute();
assert_eq!(results.results.len(), 1);
}
#[test]
fn test_refactoring_tool_execute_removes_from_cache() {
let mut rt = X86RefactoringTool::new();
rt.verbose = true;
let result = rt.execute_all();
assert!(result.success);
}
#[test]
fn test_code_quality_getters_return_correct_types() {
let cq = X86ToolingCodeQuality::new();
let metrics: &[X86ComplexityMetrics] = cq.get_complexity_metrics();
let dead: &[X86DeadCodeInfo] = cq.get_dead_code();
let dups: &[X86DuplicationInfo] = cq.get_duplications();
assert!(metrics.is_empty());
assert!(dead.is_empty());
assert!(dups.is_empty());
}
#[test]
fn test_find_symbol_getters_return_correct_types() {
let fs = X86ToolingFindSymbol::new();
let refs: &[X86SymbolRef] = fs.get_references();
let decls: &[X86SymbolRef] = fs.get_declarations();
let overs: &[X86SymbolRef] = fs.get_overrides();
let bases: &[X86SymbolRef] = fs.get_base_classes();
let derived: &[X86SymbolRef] = fs.get_derived_classes();
assert!(refs.is_empty());
assert!(decls.is_empty());
assert!(overs.is_empty());
assert!(bases.is_empty());
assert!(derived.is_empty());
}
#[test]
fn test_matcher_has_descendant_on_empty_source() {
let matchers = X86ASTMatchers::new();
let source = "";
let file = Path::new("empty.c");
let predicate = X86ASTMatcherPredicate::HasDescendant(Box::new(
X86ASTMatcherPredicate::NodeKind(X86MatchedNodeKind::ReturnStmt),
));
let matches = matchers.match_in_source(source, &predicate, file);
assert!(matches.is_empty());
}
#[test]
fn test_all_combinators_query_string_non_empty() {
let combinators = vec![
X86ASTMatchers::all_of(vec![X86ASTMatchers::anything()]).to_query_string(),
X86ASTMatchers::any_of(vec![X86ASTMatchers::anything()]).to_query_string(),
X86ASTMatchers::unless(X86ASTMatchers::anything()).to_query_string(),
X86ASTMatchers::optionally(X86ASTMatchers::anything()).to_query_string(),
X86ASTMatchers::has(X86ASTMatchers::anything()).to_query_string(),
X86ASTMatchers::for_each(X86ASTMatchers::anything()).to_query_string(),
X86ASTMatchers::for_each_descendant(X86ASTMatchers::anything()).to_query_string(),
X86ASTMatchers::find_all(X86ASTMatchers::anything()).to_query_string(),
X86ASTMatchers::anything().to_query_string(),
X86ASTMatchers::equals_node("n").to_query_string(),
X86ASTMatchers::has_string_value("s").to_query_string(),
X86ASTMatchers::has_integer_value(0).to_query_string(),
];
for qs in &combinators {
assert!(!qs.is_empty());
}
}
#[test]
fn test_range_contains_multi_line() {
let range = X86ToolingRange::new(
X86ToolingLocation::new(2, 5),
X86ToolingLocation::new(5, 10),
);
assert!(range.contains(X86ToolingLocation::new(3, 1)));
assert!(range.contains(X86ToolingLocation::new(3, 100)));
assert!(range.contains(X86ToolingLocation::new(2, 5)));
assert!(range.contains(X86ToolingLocation::new(5, 10)));
assert!(!range.contains(X86ToolingLocation::new(1, 5)));
assert!(!range.contains(X86ToolingLocation::new(6, 1)));
assert!(!range.contains(X86ToolingLocation::new(2, 4)));
assert!(!range.contains(X86ToolingLocation::new(5, 11)));
}
#[test]
fn test_compilation_db_default_values() {
let db = X86CompilationDatabase::default();
assert!(!db.loaded);
assert!(db.is_empty());
assert_eq!(db.len(), 0);
assert_eq!(db.db_path, PathBuf::new());
}
#[test]
fn test_tool_results_default_values() {
let results = X86ToolResults::new(X86ToolExecutionMode::Standalone);
assert!(results.results.is_empty());
assert!(results.aggregated.success);
assert_eq!(results.aggregated.files_processed, 0);
}
#[test]
fn test_tooling_full_summary_via_display() {
let mut tf = X86ToolingFull::new();
let path = temp_file("summary_test.c", "int main() { return 0; }");
tf.add_file(path.clone());
let _ = tf.run_all();
let summary = tf.generate_summary();
let display = format!("{}", summary);
assert!(display.contains("Files processed"));
assert!(display.contains("Operations run"));
assert!(display.contains("Errors"));
assert!(display.contains("Warnings"));
assert!(display.contains("Replacements made"));
assert!(display.contains("Files modified"));
fs::remove_file(&path).ok();
}
#[test]
fn test_find_symbol_on_empty_source() {
let fs = X86ToolingFindSymbol::new();
let refs = fs.find_references_in_source("", "foo", Path::new("empty.c"));
assert!(refs.is_empty());
}
#[test]
fn test_find_symbol_declarations_on_empty_source() {
let fs = X86ToolingFindSymbol::new();
let decls = fs.find_declarations_in_source("", "foo", Path::new("empty.c"));
assert!(decls.is_empty());
}
#[test]
fn test_detect_dead_code_on_empty_source() {
let cq = X86ToolingCodeQuality::new();
let dead = cq.detect_dead_code("", Path::new("empty.c"));
assert!(dead.is_empty());
}
#[test]
fn test_detect_duplication_on_short_source() {
let cq = X86ToolingCodeQuality::new();
let dups = cq.detect_duplication("short\n", Path::new("short.c"));
assert!(dups.is_empty());
}
#[test]
fn test_complexity_metrics_parameter_count() {
let mut cm = X86ComplexityMetrics::new("func".into());
cm.parameter_count = 3;
cm.line_count = 20;
cm.statement_count = 10;
cm.cyclomatic_complexity = 5;
cm.cognitive_complexity = 8;
cm.nesting_depth = 2;
cm.halstead_volume = 42.0;
cm.halstead_difficulty = 3.5;
cm.halstead_effort = 147.0;
let display = format!("{}", cm);
assert!(display.contains("Parameters: 3"));
assert!(display.contains("Lines: 20"));
assert!(display.contains("Statements: 10"));
assert!(display.contains("Cyclomatic Complexity: 5"));
assert!(display.contains("Cognitive Complexity: 8"));
assert!(display.contains("Nesting Depth: 2"));
}
#[test]
fn test_dead_code_info_all_fields() {
let dc = X86DeadCodeInfo {
name: "dead_func".into(),
kind: X86DeadCodeKind::UnusedFunction,
file_path: PathBuf::from("module.c"),
location: X86ToolingLocation::new(15, 1),
reason: "never called".into(),
};
assert_eq!(dc.name, "dead_func");
assert_eq!(dc.kind, X86DeadCodeKind::UnusedFunction);
assert_eq!(dc.file_path, PathBuf::from("module.c"));
assert_eq!(dc.location.line, 15);
}
#[test]
fn test_duplication_info_all_fields() {
let dup = X86DuplicationInfo {
block_a_location: X86ToolingLocation::new(10, 1),
block_a_lines: (10, 20),
block_b_location: X86ToolingLocation::new(50, 1),
block_b_lines: (50, 60),
similarity: 0.85,
line_count: 10,
file_path: PathBuf::from("test.c"),
};
assert_eq!(dup.block_a_lines, (10, 20));
assert_eq!(dup.similarity, 0.85);
assert_eq!(dup.line_count, 10);
}
#[test]
fn test_many_replacements_across_files() {
let mut rt = X86RefactoringTool::new();
for i in 0..200 {
let file = format!("file{}.c", i / 10);
rt.replace(
Path::new(&file),
X86ToolingRange::new(
X86ToolingLocation::new((i % 50) as usize + 1, 1),
X86ToolingLocation::new((i % 50) as usize + 1, 10),
),
&format!("edit_{}", i),
&format!("replacement {}", i),
);
}
assert_eq!(rt.edit_count(), 200);
}
#[test]
fn test_deeply_nested_ast_matchers() {
let inner1 = X86ASTMatchers::has_name("leaf");
let inner2 = X86ASTMatchers::all_of(vec![inner1, X86ASTMatchers::is_static()]);
let inner3 = X86ASTMatchers::has_descendant(inner2);
let inner4 = X86ASTMatchers::any_of(vec![
X86ASTMatchers::function_decl(),
inner3,
X86ASTMatchers::cxx_method_decl(),
]);
let outer = X86ASTMatchers::all_of(vec![
X86ASTMatchers::is_inline(),
inner4,
X86ASTMatchers::is_constexpr(),
]);
let qs = outer.to_query_string();
assert!(qs.contains("allOf"));
assert!(qs.contains("anyOf"));
assert!(qs.contains("hasDescendant"));
assert!(qs.contains("hasName"));
assert!(qs.contains("leaf"));
}
#[test]
fn test_all_narrowing_matchers_on_cpp_source() {
let matchers = X86ASTMatchers::new();
let source = "class Base {\n public:\n virtual void foo() = 0;\n protected:\n int x;\n private:\n void helper() { }\n};\nclass Derived final : public Base {\n void foo() override { }\n};";
let file = Path::new("test.cpp");
for predicate in &[
X86ASTMatcherPredicate::IsVirtual,
X86ASTMatcherPredicate::IsOverride,
X86ASTMatcherPredicate::IsFinal,
X86ASTMatcherPredicate::IsPublic,
X86ASTMatcherPredicate::IsProtected,
X86ASTMatcherPredicate::IsPrivate,
] {
let matches = matchers.match_in_source(source, predicate, file);
}
}
#[test]
fn test_all_expr_matchers_on_rich_source() {
let matchers = X86ASTMatchers::new();
let source = "int x = 42;\nfloat y = 3.14f;\nchar c = 'A';\nconst char* s = \"hello\";\nbool b = true;\nint* p = nullptr;\nint arr[10];\nint z = foo(x) + bar(y);";
let file = Path::new("test.c");
let kinds = [
X86MatchedNodeKind::IntegerLiteral,
X86MatchedNodeKind::FloatLiteral,
X86MatchedNodeKind::CharacterLiteral,
X86MatchedNodeKind::StringLiteral,
X86MatchedNodeKind::BooleanLiteral,
X86MatchedNodeKind::CallExpr,
X86MatchedNodeKind::BinaryOperator,
];
for kind in &kinds {
let predicate = X86ASTMatcherPredicate::NodeKind(*kind);
let matches = matchers.match_in_source(source, &predicate, file);
assert!(!matches.is_empty(), "No matches for {:?}", kind);
}
}
#[test]
fn test_tooling_full_summary_fields_populated() {
let mut tf = X86ToolingFull::new();
let path1 = temp_file("tf_summ1.c", "int a;");
let path2 = temp_file("tf_summ2.c", "int b;");
tf.add_file(path1.clone());
tf.add_file(path2.clone());
let _ = tf.run_all();
let summary = tf.generate_summary();
assert_eq!(summary.total_files, 2);
assert!(summary.total_operations > 0);
fs::remove_file(&path1).ok();
fs::remove_file(&path2).ok();
}
#[test]
fn test_refactoring_edit_converts_correctly_to_fixit_and_replacement() {
let edit = X86RefactoringEdit::replace(
PathBuf::from("conv.c"),
X86ToolingRange::new(X86ToolingLocation::new(7, 3), X86ToolingLocation::new(7, 8)),
"converted".into(),
"conversion test".into(),
);
let fixit = edit.to_fixit();
assert_eq!(fixit.replacement_text, "converted");
assert_eq!(fixit.description, "conversion test");
let repl = edit.to_replacement();
assert_eq!(repl.replacement_text, "converted");
assert_eq!(repl.file_path, PathBuf::from("conv.c"));
}
#[test]
fn test_compilation_db_load_missing_file() {
let mut db = X86CompilationDatabase::new();
let result = db.load_from_file(Path::new("/nonexistent/path/compile_commands.json"));
assert!(result.is_err());
}
#[test]
fn test_compilation_db_lookup_empty_db() {
let db = X86CompilationDatabase::new();
assert!(db.lookup(Path::new("any.c")).is_none());
assert!(db.collect_include_paths().is_empty());
}
#[test]
fn test_symbol_ref_display_contains_all_fields() {
let r = X86SymbolRef::new(
"test_sym".into(),
X86SymbolRefKind::Declaration,
PathBuf::from("header.h"),
X86ToolingLocation::new(12, 5),
);
let s = format!("{}", r);
assert!(s.contains("test_sym"));
assert!(s.contains("header.h"));
assert!(s.contains("declaration"));
}
#[test]
fn test_tooling_full_result_clone() {
let mut r = X86ToolingResult::new();
r.files_processed = 3;
r.files_modified = 2;
r.success = false;
r.diagnostics.push(X86ToolingDiagnostic {
location: X86ToolingLocation::new(1, 1),
range: None,
message: "test".into(),
severity: X86ToolingSeverity::Error,
file_path: PathBuf::from("test.c"),
fixit_hints: Vec::new(),
});
let cloned = r.clone();
assert_eq!(cloned.files_processed, 3);
assert_eq!(cloned.files_modified, 2);
assert!(!cloned.success);
assert_eq!(cloned.diagnostics.len(), 1);
}
}