#[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 X86SourceLocation {
pub line: usize,
pub column: usize,
}
impl X86SourceLocation {
pub fn new(line: usize, column: usize) -> Self {
Self { line, column }
}
}
impl fmt::Display for X86SourceLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct X86SourceRange {
pub start_line: usize,
pub start_col: usize,
pub end_line: usize,
pub end_col: usize,
}
impl X86SourceRange {
pub fn new(start_line: usize, start_col: usize, end_line: usize, end_col: usize) -> Self {
Self {
start_line,
start_col,
end_line,
end_col,
}
}
pub fn from_locations(start: X86SourceLocation, end: X86SourceLocation) -> Self {
Self {
start_line: start.line,
start_col: start.column,
end_line: end.line,
end_col: end.column,
}
}
pub fn contains(&self, line: usize, col: usize) -> bool {
if line < self.start_line || line > self.end_line {
return false;
}
if line == self.start_line && col < self.start_col {
return false;
}
if line == self.end_line && col > self.end_col {
return false;
}
true
}
pub fn overlaps(&self, other: &X86SourceRange) -> bool {
if self.end_line < other.start_line {
return false;
}
if self.start_line > other.end_line {
return false;
}
if self.end_line == other.start_line && self.end_col < other.start_col {
return false;
}
if self.start_line == other.end_line && self.start_col > other.end_col {
return false;
}
true
}
}
impl fmt::Display for X86SourceRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}:{}-{}:{}",
self.start_line, self.start_col, self.end_line, self.end_col
)
}
}
#[derive(Debug, Clone)]
pub struct X86Replacement {
pub file_path: PathBuf,
pub offset: usize,
pub length: usize,
pub replacement_text: String,
pub description: String,
}
impl X86Replacement {
pub fn new(
file_path: impl Into<PathBuf>,
offset: usize,
length: usize,
replacement_text: impl Into<String>,
description: impl Into<String>,
) -> Self {
Self {
file_path: file_path.into(),
offset,
length,
replacement_text: replacement_text.into(),
description: description.into(),
}
}
pub fn from_position(
file_path: impl Into<PathBuf>,
start: X86SourceLocation,
end: X86SourceLocation,
source: &str,
replacement_text: impl Into<String>,
description: impl Into<String>,
) -> Self {
let offset = Self::line_col_to_offset(source, start.line, start.column);
let end_offset = Self::line_col_to_offset(source, end.line, end.column);
let length = end_offset.saturating_sub(offset);
Self {
file_path: file_path.into(),
offset,
length,
replacement_text: replacement_text.into(),
description: description.into(),
}
}
pub fn line_col_to_offset(source: &str, line: usize, col: usize) -> usize {
let mut offset = 0;
for (current_line, _) in source.lines().enumerate() {
if current_line + 1 >= line {
break;
}
offset += source
.lines()
.nth(current_line)
.map(|l| l.len() + 1)
.unwrap_or(0);
}
offset + col.saturating_sub(1)
}
pub fn apply(&self, source: &str) -> Result<String, String> {
if self.offset > source.len() {
return Err(format!(
"Offset {} exceeds source length {}",
self.offset,
source.len()
));
}
let end = (self.offset + self.length).min(source.len());
let mut result = String::with_capacity(source.len() + self.replacement_text.len());
result.push_str(&source[..self.offset]);
result.push_str(&self.replacement_text);
result.push_str(&source[end..]);
Ok(result)
}
pub fn conflicts_with(&self, other: &X86Replacement) -> bool {
if self.file_path != other.file_path {
return false;
}
let self_end = self.offset + self.length;
let other_end = other.offset + other.length;
if self.offset < other_end && other.offset < self_end {
return true;
}
if self_end == other.offset || other_end == self.offset {
return true;
}
false
}
}
impl fmt::Display for X86Replacement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}: offset={}, length={}, replacement=\"{}\", description=\"{}\"",
self.file_path.display(),
self.offset,
self.length,
self.replacement_text,
self.description
)
}
}
#[derive(Debug)]
pub struct X86ClangToolsFull {
pub apply_replacements: X86ClangApplyReplacements,
pub change_namespace: X86ClangChangeNamespace,
pub doc: X86ClangDoc,
pub include_fixer: X86ClangIncludeFixer,
pub clang_move: X86ClangMove,
pub rename: X86ClangRename,
pub reorder_fields: X86ClangReorderFields,
pub scan_deps: X86ScanDeps,
pub diag_tool: X86DiagTool,
pub working_dir: PathBuf,
pub source_files: Vec<PathBuf>,
pub verbose: bool,
}
impl X86ClangToolsFull {
pub fn new() -> Self {
Self {
apply_replacements: X86ClangApplyReplacements::new(),
change_namespace: X86ClangChangeNamespace::new(),
doc: X86ClangDoc::new(),
include_fixer: X86ClangIncludeFixer::new(),
clang_move: X86ClangMove::new(),
rename: X86ClangRename::new(),
reorder_fields: X86ClangReorderFields::new(),
scan_deps: X86ScanDeps::new(),
diag_tool: X86DiagTool::new(),
working_dir: PathBuf::from("."),
source_files: Vec::new(),
verbose: false,
}
}
pub fn with_working_dir<P: AsRef<Path>>(dir: P) -> Self {
let mut tools = Self::new();
tools.working_dir = dir.as_ref().to_path_buf();
tools
}
pub fn add_file<P: AsRef<Path>>(&mut self, path: P) {
self.source_files.push(path.as_ref().to_path_buf());
}
pub fn set_verbose(&mut self, verbose: bool) {
self.verbose = verbose;
self.apply_replacements.verbose = verbose;
self.change_namespace.verbose = verbose;
self.doc.verbose = verbose;
self.include_fixer.verbose = verbose;
self.clang_move.verbose = verbose;
self.rename.verbose = verbose;
self.reorder_fields.verbose = verbose;
self.scan_deps.verbose = verbose;
self.diag_tool.verbose = verbose;
}
pub fn run_all(&mut self) -> X86ToolsFullResult {
let mut result = X86ToolsFullResult::new();
for file in self.source_files.clone() {
if let Ok(replacements) = self.apply_replacements.process_file(&file) {
result.replacements_applied += replacements;
}
if let Ok(changes) = self.change_namespace.process_file(&file) {
result.namespace_changes += changes;
}
if let Ok(_docs) = self.doc.extract_from_file(&file) {
result.files_documented += 1;
}
if let Ok(patches) = self.include_fixer.fix_file(&file) {
result.includes_fixed += patches;
}
if let Ok(moves) = self.clang_move.process_file(&file) {
result.moves_applied += moves;
}
if let Ok(renames) = self.rename.process_file(&file) {
result.renames_applied += renames;
}
if let Ok(reorders) = self.reorder_fields.process_file(&file) {
result.fields_reordered += reorders;
}
if let Ok(deps) = self.scan_deps.scan_file(&file) {
result.deps_scanned += deps.len();
}
if let Ok(diags) = self.diag_tool.analyze_file(&file) {
result.diagnostics_found += diags;
}
}
result
}
}
impl Default for X86ClangToolsFull {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default)]
pub struct X86ToolsFullResult {
pub replacements_applied: usize,
pub namespace_changes: usize,
pub files_documented: usize,
pub includes_fixed: usize,
pub moves_applied: usize,
pub renames_applied: usize,
pub fields_reordered: usize,
pub deps_scanned: usize,
pub diagnostics_found: usize,
}
impl X86ToolsFullResult {
pub fn new() -> Self {
Self::default()
}
pub fn total_operations(&self) -> usize {
self.replacements_applied
+ self.namespace_changes
+ self.files_documented
+ self.includes_fixed
+ self.moves_applied
+ self.renames_applied
+ self.fields_reordered
+ self.deps_scanned
+ self.diagnostics_found
}
pub fn summary(&self) -> String {
let mut s = String::new();
s.push_str("╔══════════════════════════════════════════╗\n");
s.push_str("║ X86 Full Tooling Results ║\n");
s.push_str("╠══════════════════════════════════════════╣\n");
s.push_str(&format!(
"║ Replacements applied: {:>8} ║\n",
self.replacements_applied
));
s.push_str(&format!(
"║ Namespace changes: {:>8} ║\n",
self.namespace_changes
));
s.push_str(&format!(
"║ Files documented: {:>8} ║\n",
self.files_documented
));
s.push_str(&format!(
"║ Includes fixed: {:>8} ║\n",
self.includes_fixed
));
s.push_str(&format!(
"║ Moves applied: {:>8} ║\n",
self.moves_applied
));
s.push_str(&format!(
"║ Renames applied: {:>8} ║\n",
self.renames_applied
));
s.push_str(&format!(
"║ Fields reordered: {:>8} ║\n",
self.fields_reordered
));
s.push_str(&format!(
"║ Deps scanned: {:>8} ║\n",
self.deps_scanned
));
s.push_str(&format!(
"║ Diagnostics found: {:>8} ║\n",
self.diagnostics_found
));
s.push_str("╚══════════════════════════════════════════╝\n");
s
}
}
#[derive(Debug, Clone)]
pub struct X86ApplyReplacementsConfig {
pub yaml_file: Option<PathBuf>,
pub dry_run: bool,
pub deduplicate: bool,
pub detect_conflicts: bool,
pub format: bool,
pub style: X86ReplacementFileStyle,
}
impl Default for X86ApplyReplacementsConfig {
fn default() -> Self {
Self {
yaml_file: None,
dry_run: false,
deduplicate: true,
detect_conflicts: true,
format: true,
style: X86ReplacementFileStyle::YAML,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ReplacementFileStyle {
YAML,
JSON,
}
#[derive(Debug, Clone)]
pub struct X86ApplyReplacementsResult {
pub file: PathBuf,
pub replacements: Vec<X86Replacement>,
pub conflicts: Vec<(X86Replacement, X86Replacement)>,
pub applied: bool,
pub error: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86ClangApplyReplacements {
pub config: X86ApplyReplacementsConfig,
pub replacements: HashMap<PathBuf, Vec<X86Replacement>>,
pub source_cache: HashMap<PathBuf, String>,
pub conflicts: Vec<(X86Replacement, X86Replacement)>,
pub stats: X86ApplyReplacementsStats,
pub verbose: bool,
}
#[derive(Debug, Clone, Default)]
pub struct X86ApplyReplacementsStats {
pub files_processed: usize,
pub replacements_loaded: usize,
pub replacements_applied: usize,
pub replacements_skipped: usize,
pub conflicts_detected: usize,
pub conflicts_resolved: usize,
}
impl X86ClangApplyReplacements {
pub fn new() -> Self {
Self {
config: X86ApplyReplacementsConfig::default(),
replacements: HashMap::new(),
source_cache: HashMap::new(),
conflicts: Vec::new(),
stats: X86ApplyReplacementsStats::default(),
verbose: false,
}
}
pub fn load_yaml_file<P: AsRef<Path>>(&mut self, path: P) -> Result<usize, String> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.map_err(|e| format!("Cannot read YAML file '{}': {}", path.display(), e))?;
self.parse_yaml_replacements(&content, path)
}
pub fn parse_yaml_replacements(
&mut self,
content: &str,
_source_path: &Path,
) -> Result<usize, String> {
let mut count = 0;
let mut current_file: Option<String> = None;
let mut in_replacements = false;
let mut offset: Option<usize> = None;
let mut length: Option<usize> = None;
let mut replacement_text: Option<String> = None;
let mut file_path: Option<String> = None;
for line in content.lines() {
let trimmed = line.trim();
if trimmed == "---" || trimmed == "..." || trimmed.starts_with('#') {
continue;
}
if trimmed == "Replacements:" {
in_replacements = true;
continue;
}
if trimmed.starts_with("MainSourceFile:") {
if let Some(val) = Self::parse_yaml_value(trimmed, "MainSourceFile") {
current_file = Some(val);
}
continue;
}
if in_replacements && trimmed.starts_with('-') {
if let (Some(fp), Some(off), Some(len), Some(rt)) =
(&file_path, offset, length, replacement_text.clone())
{
let entry = self.add_replacement_from_yaml(fp, off, len, &rt);
if entry.is_ok() {
count += 1;
}
}
offset = None;
length = None;
replacement_text = None;
file_path = current_file.clone();
continue;
}
if in_replacements {
if let Some(val) = Self::parse_yaml_value(trimmed, "FilePath") {
file_path = Some(val);
}
if let Some(val) = Self::parse_yaml_int(trimmed, "Offset") {
offset = Some(val);
}
if let Some(val) = Self::parse_yaml_int(trimmed, "Length") {
length = Some(val);
}
if let Some(val) = Self::parse_yaml_value(trimmed, "ReplacementText") {
replacement_text = Some(val);
}
}
}
if let (Some(fp), Some(off), Some(len), Some(rt)) =
(&file_path, offset, length, replacement_text)
{
let entry = self.add_replacement_from_yaml(fp, off, len, &rt);
if entry.is_ok() {
count += 1;
}
}
self.stats.replacements_loaded += count;
if self.config.detect_conflicts {
self.detect_all_conflicts();
}
Ok(count)
}
pub fn load_json_file<P: AsRef<Path>>(&mut self, path: P) -> Result<usize, String> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.map_err(|e| format!("Cannot read JSON file '{}': {}", path.display(), e))?;
self.parse_json_replacements(&content, path)
}
pub fn parse_json_replacements(
&mut self,
content: &str,
_source_path: &Path,
) -> Result<usize, String> {
let mut count = 0;
let trimmed = content.trim();
if !trimmed.starts_with('[') {
return Err("JSON replacements must be an array".into());
}
let inner = &trimmed[1..trimmed.len() - 1];
let mut depth = 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 Ok(repl) = self.parse_json_replacement_obj(obj_str) {
count += 1;
self.add_replacement(repl);
}
}
obj_start = None;
}
}
_ => {}
}
}
self.stats.replacements_loaded += count;
if self.config.detect_conflicts {
self.detect_all_conflicts();
}
Ok(count)
}
fn parse_json_replacement_obj(&self, obj: &str) -> Result<X86Replacement, String> {
let file_path =
Self::extract_json_string(obj, "FilePath").unwrap_or_else(|| "unknown".to_string());
let offset = Self::extract_json_int(obj, "Offset").unwrap_or(0);
let length = Self::extract_json_int(obj, "Length").unwrap_or(0);
let text =
Self::extract_json_string(obj, "ReplacementText").unwrap_or_else(|| String::new());
let desc = Self::extract_json_string(obj, "Description")
.unwrap_or_else(|| "JSON replacement".to_string());
Ok(X86Replacement::new(file_path, offset, length, text, desc))
}
fn extract_json_string(obj: &str, key: &str) -> Option<String> {
let search = format!("\"{}\"", key);
let pos = obj.find(&search)?;
let after_key = &obj[pos + search.len()..];
let colon_pos = after_key.find(':')?;
let after_colon = &after_key[colon_pos + 1..].trim();
if after_colon.starts_with('"') {
let content = &after_colon[1..];
if let Some(end) = content.find('"') {
return Some(content[..end].to_string());
}
}
None
}
fn extract_json_int(obj: &str, key: &str) -> Option<usize> {
let search = format!("\"{}\"", key);
let pos = obj.find(&search)?;
let after_key = &obj[pos + search.len()..];
let colon_pos = after_key.find(':')?;
let after_colon = after_key[colon_pos + 1..].trim();
let num_str: String = after_colon
.chars()
.take_while(|c| c.is_ascii_digit())
.collect();
num_str.parse().ok()
}
fn parse_yaml_value(line: &str, key: &str) -> Option<String> {
let prefix = format!("{}:", key);
let trimmed = line.trim();
if trimmed.starts_with(&prefix) {
let val = trimmed[prefix.len()..].trim();
let val = val.trim_matches('\'');
let val = val.trim_matches('"');
Some(val.to_string())
} else {
None
}
}
fn parse_yaml_int(line: &str, key: &str) -> Option<usize> {
let prefix = format!("{}:", key);
let trimmed = line.trim();
if trimmed.starts_with(&prefix) {
let val = trimmed[prefix.len()..].trim();
val.parse().ok()
} else {
None
}
}
fn add_replacement_from_yaml(
&mut self,
file_path: &str,
offset: usize,
length: usize,
text: &str,
) -> Result<(), String> {
let repl = X86Replacement::new(
PathBuf::from(file_path),
offset,
length,
text,
format!("YAML replacement at offset {}", offset),
);
self.add_replacement(repl);
Ok(())
}
pub fn add_replacement(&mut self, replacement: X86Replacement) {
let key = replacement.file_path.clone();
let entry = self.replacements.entry(key).or_default();
if self.config.deduplicate {
let is_dup = entry.iter().any(|r| {
r.offset == replacement.offset
&& r.length == replacement.length
&& r.replacement_text == replacement.replacement_text
});
if !is_dup {
entry.push(replacement);
} else {
self.stats.replacements_skipped += 1;
}
} else {
entry.push(replacement);
}
}
pub fn detect_all_conflicts(&mut self) -> usize {
self.conflicts.clear();
let mut conflict_count = 0;
for (_, replacements) in &self.replacements {
let sorted: Vec<&X86Replacement> = {
let mut v: Vec<&X86Replacement> = replacements.iter().collect();
v.sort_by_key(|r| r.offset);
v
};
for i in 0..sorted.len() {
for j in (i + 1)..sorted.len() {
if sorted[i].conflicts_with(sorted[j]) {
self.conflicts.push((sorted[i].clone(), sorted[j].clone()));
conflict_count += 1;
}
}
}
}
self.stats.conflicts_detected = conflict_count;
conflict_count
}
pub fn resolve_conflicts(&mut self, strategy: X86ConflictResolution) -> usize {
let mut resolved = 0;
match strategy {
X86ConflictResolution::KeepFirst => {
let mut to_remove: Vec<(PathBuf, usize)> = Vec::new();
for (_, second) in &self.conflicts {
to_remove.push((second.file_path.clone(), second.offset));
}
for (path, offset) in &to_remove {
if let Some(repls) = self.replacements.get_mut(path) {
repls.retain(|r| r.offset != *offset);
resolved += 1;
}
}
}
X86ConflictResolution::KeepLast => {
let mut to_remove: Vec<(PathBuf, usize)> = Vec::new();
for (first, _) in &self.conflicts {
to_remove.push((first.file_path.clone(), first.offset));
}
for (path, offset) in &to_remove {
if let Some(repls) = self.replacements.get_mut(path) {
repls.retain(|r| r.offset != *offset);
resolved += 1;
}
}
}
X86ConflictResolution::Merge => {
let mut merged = 0;
for (a, b) in &self.conflicts.clone() {
if a.offset + a.length == b.offset {
let merged_text = format!("{}{}", a.replacement_text, b.replacement_text);
let merged_repl = X86Replacement::new(
a.file_path.clone(),
a.offset,
a.length + b.length,
merged_text,
"Merged replacements",
);
if let Some(repls) = self.replacements.get_mut(&a.file_path) {
repls.retain(|r| r.offset != a.offset && r.offset != b.offset);
repls.push(merged_repl);
merged += 1;
}
}
}
resolved = merged;
}
}
self.stats.conflicts_resolved += resolved;
self.detect_all_conflicts(); resolved
}
pub fn process_file<P: AsRef<Path>>(&mut self, path: P) -> Result<usize, String> {
let path = path.as_ref();
let content = self.load_source(path)?;
let replacements = self.replacements.get(path).cloned().unwrap_or_default();
if replacements.is_empty() {
return Ok(0);
}
if self.config.dry_run {
self.stats.files_processed += 1;
return Ok(replacements.len());
}
let mut sorted: Vec<X86Replacement> = replacements.clone();
sorted.sort_by_key(|r| r.offset);
sorted.reverse();
let mut modified = content;
let mut applied = 0;
for replacement in &sorted {
match replacement.apply(&modified) {
Ok(new_content) => {
modified = new_content;
applied += 1;
}
Err(e) => {
if self.verbose {
eprintln!("Failed to apply replacement in {}: {}", path.display(), e);
}
}
}
}
if applied > 0 {
fs::write(path, &modified)
.map_err(|e| format!("Cannot write to '{}': {}", path.display(), e))?;
}
self.stats.files_processed += 1;
self.stats.replacements_applied += applied;
Ok(applied)
}
fn load_source(&mut self, path: &Path) -> Result<String, String> {
if let Some(content) = self.source_cache.get(path) {
return Ok(content.clone());
}
let content = fs::read_to_string(path)
.map_err(|e| format!("Cannot read '{}': {}", path.display(), e))?;
self.source_cache
.insert(path.to_path_buf(), content.clone());
Ok(content)
}
pub fn files_with_replacements(&self) -> Vec<&PathBuf> {
self.replacements.keys().collect()
}
pub fn total_replacements(&self) -> usize {
self.replacements.values().map(|v| v.len()).sum()
}
}
impl Default for X86ClangApplyReplacements {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ConflictResolution {
KeepFirst,
KeepLast,
Merge,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct X86NamespaceComponent {
pub name: String,
pub is_anonymous: bool,
pub is_inline: bool,
}
impl X86NamespaceComponent {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
is_anonymous: false,
is_inline: false,
}
}
pub fn anonymous() -> Self {
Self {
name: "anonymous".to_string(),
is_anonymous: true,
is_inline: false,
}
}
pub fn is_nested(&self) -> bool {
!self.is_anonymous && !self.name.is_empty()
}
}
impl fmt::Display for X86NamespaceComponent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_anonymous {
write!(f, "<anonymous>")
} else {
write!(f, "{}", self.name)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct X86NamespacePath {
pub components: Vec<X86NamespaceComponent>,
}
impl X86NamespacePath {
pub fn empty() -> Self {
Self {
components: Vec::new(),
}
}
pub fn from_str(s: &str) -> Self {
if s.is_empty() {
return Self::empty();
}
let components: Vec<X86NamespaceComponent> = s
.split("::")
.filter(|c| !c.is_empty())
.map(|c| X86NamespaceComponent::new(c))
.collect();
Self { components }
}
pub fn to_string(&self) -> String {
if self.components.is_empty() {
return String::new();
}
self.components
.iter()
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join("::")
}
pub fn is_empty(&self) -> bool {
self.components.is_empty()
}
pub fn push(&mut self, component: X86NamespaceComponent) {
self.components.push(component);
}
pub fn parent(&self) -> Option<X86NamespacePath> {
if self.components.len() <= 1 {
None
} else {
Some(X86NamespacePath {
components: self.components[..self.components.len() - 1].to_vec(),
})
}
}
pub fn leaf(&self) -> Option<&X86NamespaceComponent> {
self.components.last()
}
}
impl fmt::Display for X86NamespacePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_string())
}
}
#[derive(Debug, Clone)]
pub struct X86NamespaceDeclaration {
pub name: String,
pub qualified_name: String,
pub source_namespace: X86NamespacePath,
pub target_namespace: X86NamespacePath,
pub decl_type: X86NamespaceDeclType,
pub file_path: PathBuf,
pub range: X86SourceRange,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86NamespaceDeclType {
Function,
Class,
Struct,
Enum,
Variable,
TypeAlias,
UsingDeclaration,
TemplateFunction,
TemplateClass,
EnumClass,
}
impl fmt::Display for X86NamespaceDeclType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Function => write!(f, "function"),
Self::Class => write!(f, "class"),
Self::Struct => write!(f, "struct"),
Self::Enum => write!(f, "enum"),
Self::Variable => write!(f, "variable"),
Self::TypeAlias => write!(f, "type alias"),
Self::UsingDeclaration => write!(f, "using declaration"),
Self::TemplateFunction => write!(f, "template function"),
Self::TemplateClass => write!(f, "template class"),
Self::EnumClass => write!(f, "enum class"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86ClangChangeNamespace {
pub source_namespace: X86NamespacePath,
pub target_namespace: X86NamespacePath,
pub declarations: Vec<X86NamespaceDeclaration>,
pub update_qualifiers: bool,
pub fix_using_declarations: bool,
pub handle_template_specializations: bool,
pub process_nested_namespaces: bool,
pub references: Vec<X86NamespaceReference>,
pub source_cache: HashMap<PathBuf, String>,
pub verbose: bool,
}
#[derive(Debug, Clone)]
pub struct X86NamespaceReference {
pub symbol_name: String,
pub qualified_name: String,
pub file_path: PathBuf,
pub range: X86SourceRange,
pub ref_type: X86NamespaceRefType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86NamespaceRefType {
QualifiedUse,
UnqualifiedUse,
UsingDeclaration,
UsingDirective,
ForwardDeclaration,
FriendDeclaration,
TemplateSpecialization,
}
impl X86ClangChangeNamespace {
pub fn new() -> Self {
Self {
source_namespace: X86NamespacePath::empty(),
target_namespace: X86NamespacePath::empty(),
declarations: Vec::new(),
update_qualifiers: true,
fix_using_declarations: true,
handle_template_specializations: true,
process_nested_namespaces: true,
references: Vec::new(),
source_cache: HashMap::new(),
verbose: false,
}
}
pub fn set_namespaces(&mut self, from: &str, to: &str) {
self.source_namespace = X86NamespacePath::from_str(from);
self.target_namespace = X86NamespacePath::from_str(to);
}
pub fn scan_namespace_contents<P: AsRef<Path>>(
&mut self,
path: P,
namespace: &X86NamespacePath,
) -> Result<Vec<X86NamespaceDeclaration>, String> {
let path = path.as_ref();
let content = self.load_source(path)?;
let mut decls = Vec::new();
let ns_str = namespace.to_string();
let in_namespace = ns_str.is_empty() || content.contains(&format!("namespace {}", ns_str));
if !in_namespace && !ns_str.is_empty() {
return Ok(decls);
}
let lines: Vec<&str> = content.lines().collect();
let mut in_target_ns = ns_str.is_empty();
let mut ns_depth: i32 = 0;
for (line_idx, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with("namespace ") {
if !ns_str.is_empty() && trimmed.contains(&ns_str) {
in_target_ns = true;
}
if trimmed.contains('{') {
ns_depth += 1;
}
}
if trimmed == "}" && in_target_ns && ns_depth > 0 {
ns_depth -= 1;
if ns_depth == 0 && !ns_str.is_empty() {
in_target_ns = false;
}
}
if !in_target_ns {
continue;
}
let decl_type = Self::classify_declaration_line(trimmed);
if decl_type.is_some() {
if let Some(name) = Self::extract_decl_name(trimmed) {
let qualified = if ns_str.is_empty() {
name.clone()
} else {
format!("{}::{}", ns_str, name)
};
decls.push(X86NamespaceDeclaration {
name,
qualified_name: qualified,
source_namespace: namespace.clone(),
target_namespace: self.target_namespace.clone(),
decl_type: decl_type.unwrap(),
file_path: path.to_path_buf(),
range: X86SourceRange::new(line_idx + 1, 1, line_idx + 1, trimmed.len()),
});
}
}
}
Ok(decls)
}
fn classify_declaration_line(line: &str) -> Option<X86NamespaceDeclType> {
let trimmed = line.trim();
if trimmed.starts_with("template<") || trimmed.starts_with("template <") {
if trimmed.contains("class ") || trimmed.contains("struct ") {
return Some(X86NamespaceDeclType::TemplateClass);
}
return Some(X86NamespaceDeclType::TemplateFunction);
}
if trimmed.starts_with("class ") {
return Some(X86NamespaceDeclType::Class);
}
if trimmed.starts_with("struct ") {
return Some(X86NamespaceDeclType::Struct);
}
if trimmed.starts_with("enum class ") {
return Some(X86NamespaceDeclType::EnumClass);
}
if trimmed.starts_with("enum ") {
return Some(X86NamespaceDeclType::Enum);
}
if trimmed.starts_with("using ") {
return Some(X86NamespaceDeclType::UsingDeclaration);
}
if trimmed.starts_with("typedef ") {
return Some(X86NamespaceDeclType::TypeAlias);
}
if trimmed.contains('(') && (trimmed.contains(')') || trimmed.ends_with(')')) {
if !trimmed.starts_with("if ")
&& !trimmed.starts_with("for ")
&& !trimmed.starts_with("while ")
&& !trimmed.starts_with("switch ")
{
return Some(X86NamespaceDeclType::Function);
}
}
if !trimmed.starts_with('#')
&& !trimmed.starts_with("//")
&& !trimmed.starts_with("/*")
&& !trimmed.contains('(')
&& trimmed.contains(' ')
&& !trimmed.ends_with(';')
{
}
None
}
fn extract_decl_name(line: &str) -> Option<String> {
let trimmed = line.trim();
let without_template = if trimmed.starts_with("template") {
if let Some(pos) = trimmed.find('>') {
trimmed[pos + 1..].trim().to_string()
} else {
trimmed.to_string()
}
} else {
trimmed.to_string()
};
let words: Vec<&str> = without_template.split_whitespace().collect();
let mut idx = 0;
while idx < words.len() {
let w = words[idx];
if w == "class"
|| w == "struct"
|| w == "enum"
|| w == "typename"
|| w == "typedef"
|| w == "using"
|| w == "auto"
|| w == "const"
|| w == "constexpr"
|| w == "inline"
|| w == "static"
|| w == "virtual"
|| w == "explicit"
|| w == "friend"
|| w == "extern"
{
idx += 1;
} else {
break;
}
}
if idx < words.len() {
let name = words[idx];
let name =
name.trim_end_matches(|c: char| c == ';' || c == '(' || c == '{' || c == ':');
if !name.is_empty() {
return Some(name.to_string());
}
}
None
}
pub fn find_references(
&mut self,
decl: &X86NamespaceDeclaration,
) -> Vec<X86NamespaceReference> {
let mut refs = Vec::new();
for (path, content) in &self.source_cache.clone() {
let lines: Vec<&str> = content.lines().collect();
for (line_idx, line) in lines.iter().enumerate() {
if line.contains(&decl.name) {
if line.contains(&decl.qualified_name) {
refs.push(X86NamespaceReference {
symbol_name: decl.name.clone(),
qualified_name: decl.qualified_name.clone(),
file_path: path.clone(),
range: X86SourceRange::new(line_idx + 1, 1, line_idx + 1, line.len()),
ref_type: X86NamespaceRefType::QualifiedUse,
});
} else if Self::is_symbol_use(line, &decl.name) {
refs.push(X86NamespaceReference {
symbol_name: decl.name.clone(),
qualified_name: decl.qualified_name.clone(),
file_path: path.clone(),
range: X86SourceRange::new(line_idx + 1, 1, line_idx + 1, line.len()),
ref_type: X86NamespaceRefType::UnqualifiedUse,
});
}
}
}
}
refs
}
fn is_symbol_use(line: &str, name: &str) -> bool {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") || trimmed.starts_with('*') {
return false;
}
if trimmed.starts_with('#') {
return false;
}
if trimmed.starts_with(&format!("class {}", name))
|| trimmed.starts_with(&format!("struct {}", name))
|| trimmed.starts_with(&format!("enum {}", name))
{
return false;
}
trimmed.contains(name)
}
pub fn generate_move_patch(&self, decl: &X86NamespaceDeclaration) -> String {
let mut patch = String::new();
if self.update_qualifiers && !decl.target_namespace.is_empty() {
patch.push_str(&format!(
"// Moved from {} to {}\n",
decl.source_namespace, decl.target_namespace
));
}
patch.push_str(&format!(
"// Declaration '{}' requires namespace change: {} -> {}\n",
decl.name, decl.source_namespace, decl.target_namespace
));
if self.fix_using_declarations {
patch.push_str(&format!(
"// Consider adding: using {}::{};\n",
decl.source_namespace, decl.name
));
}
patch
}
pub fn process_file<P: AsRef<Path>>(&mut self, path: P) -> Result<usize, String> {
let path = path.as_ref();
let _content = self.load_source(path)?;
let mut changes = 0;
let ns = self.source_namespace.clone();
if let Ok(decls) = self.scan_namespace_contents(path, &ns) {
for decl in &decls {
if self.handle_template_specializations
|| !matches!(
decl.decl_type,
X86NamespaceDeclType::TemplateClass
| X86NamespaceDeclType::TemplateFunction
)
{
let refs = self.find_references(decl);
self.references.extend(refs);
if !self.source_namespace.is_empty() && !self.target_namespace.is_empty() {
let content = self.load_source(path)?;
let new_content = Self::replace_namespace_in_source(
&content,
&self.source_namespace,
&self.target_namespace,
&decl.name,
);
if new_content != content {
self.source_cache
.insert(path.to_path_buf(), new_content.clone());
changes += 1;
}
}
}
}
self.declarations.extend(decls);
}
Ok(changes)
}
fn replace_namespace_in_source(
source: &str,
from: &X86NamespacePath,
to: &X86NamespacePath,
_symbol: &str,
) -> String {
let from_str = from.to_string();
let to_str = to.to_string();
if from_str.is_empty() || to_str.is_empty() {
return source.to_string();
}
let mut result = source.to_string();
let old_ns = format!("namespace {}", from_str);
let new_ns = format!("namespace {}", to_str);
result = result.replace(&old_ns, &new_ns);
let old_qual = format!("{}::", from_str);
let new_qual = format!("{}::", to_str);
result = result.replace(&old_qual, &new_qual);
result
}
fn load_source(&mut self, path: &Path) -> Result<String, String> {
if let Some(content) = self.source_cache.get(path) {
return Ok(content.clone());
}
let content = fs::read_to_string(path)
.map_err(|e| format!("Cannot read '{}': {}", path.display(), e))?;
self.source_cache
.insert(path.to_path_buf(), content.clone());
Ok(content)
}
pub fn commit_changes(&self) -> Result<usize, String> {
let mut written = 0;
for (path, content) in &self.source_cache {
fs::write(path, content)
.map_err(|e| format!("Cannot write '{}': {}", path.display(), e))?;
written += 1;
}
Ok(written)
}
}
impl Default for X86ClangChangeNamespace {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86DocComment {
pub symbol_name: String,
pub symbol_kind: X86DocSymbolKind,
pub brief: String,
pub full_text: String,
pub params: Vec<X86DocParam>,
pub return_desc: Option<String>,
pub exceptions: Vec<X86DocException>,
pub see_also: Vec<String>,
pub related: Vec<String>,
pub notes: Vec<String>,
pub warnings: Vec<String>,
pub preconditions: Vec<String>,
pub postconditions: Vec<String>,
pub deprecated: Option<String>,
pub since: Option<String>,
pub authors: Vec<String>,
pub file_path: PathBuf,
pub line: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum X86DocSymbolKind {
Function,
Class,
Struct,
Enum,
Variable,
Macro,
TypeAlias,
Namespace,
Template,
EnumValue,
MemberFunction,
MemberVariable,
Constructor,
Destructor,
Operator,
}
impl fmt::Display for X86DocSymbolKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Function => write!(f, "function"),
Self::Class => write!(f, "class"),
Self::Struct => write!(f, "struct"),
Self::Enum => write!(f, "enum"),
Self::Variable => write!(f, "variable"),
Self::Macro => write!(f, "macro"),
Self::TypeAlias => write!(f, "typedef"),
Self::Namespace => write!(f, "namespace"),
Self::Template => write!(f, "template"),
Self::EnumValue => write!(f, "enum value"),
Self::MemberFunction => write!(f, "member function"),
Self::MemberVariable => write!(f, "member variable"),
Self::Constructor => write!(f, "constructor"),
Self::Destructor => write!(f, "destructor"),
Self::Operator => write!(f, "operator"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86DocParam {
pub name: String,
pub description: String,
pub direction: X86DocParamDirection,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DocParamDirection {
In,
Out,
InOut,
}
impl fmt::Display for X86DocParamDirection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::In => write!(f, "in"),
Self::Out => write!(f, "out"),
Self::InOut => write!(f, "in,out"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86DocException {
pub exception_type: String,
pub description: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DocOutputFormat {
Markdown,
HTML,
ManPage,
YAML,
JSON,
RST,
PlainText,
}
#[derive(Debug, Clone)]
pub struct X86ClangDoc {
pub comments: Vec<X86DocComment>,
pub output_format: X86DocOutputFormat,
pub output_dir: Option<PathBuf>,
pub generate_cross_refs: bool,
pub include_source_locations: bool,
pub project_name: String,
pub project_version: String,
pub verbose: bool,
}
impl X86ClangDoc {
pub fn new() -> Self {
Self {
comments: Vec::new(),
output_format: X86DocOutputFormat::Markdown,
output_dir: None,
generate_cross_refs: true,
include_source_locations: true,
project_name: String::new(),
project_version: String::new(),
verbose: false,
}
}
pub fn extract_from_file<P: AsRef<Path>>(
&mut self,
path: P,
) -> Result<Vec<X86DocComment>, String> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.map_err(|e| format!("Cannot read '{}': {}", path.display(), e))?;
self.extract_from_source(&content, path)
}
pub fn extract_from_source(
&mut self,
source: &str,
path: &Path,
) -> Result<Vec<X86DocComment>, String> {
let mut comments = Vec::new();
let lines: Vec<&str> = source.lines().collect();
let mut i = 0;
while i < lines.len() {
let line = lines[i].trim();
if line.starts_with("///") || line.starts_with("//!") {
let doc_text = Self::extract_triple_slash_comment(&lines, &mut i);
if let Some(comment) = self.parse_doc_comment(&doc_text, &lines, i, path) {
comments.push(comment);
}
continue;
}
if line.starts_with("/**") {
let doc_text = Self::extract_javadoc_comment(&lines, &mut i);
if let Some(comment) = self.parse_doc_comment(&doc_text, &lines, i, path) {
comments.push(comment);
}
continue;
}
if line.starts_with("/*!") {
let doc_text = Self::extract_javadoc_comment(&lines, &mut i);
if let Some(comment) = self.parse_doc_comment(&doc_text, &lines, i, path) {
comments.push(comment);
}
continue;
}
i += 1;
}
self.comments.extend(comments.clone());
Ok(comments)
}
fn extract_triple_slash_comment(lines: &[&str], idx: &mut usize) -> String {
let mut doc_lines = Vec::new();
while *idx < lines.len() {
let line = lines[*idx];
let trimmed = line.trim();
if trimmed.starts_with("///") {
doc_lines.push(trimmed[3..].trim().to_string());
} else if trimmed.starts_with("//!") {
doc_lines.push(trimmed[3..].trim().to_string());
} else {
break;
}
*idx += 1;
}
doc_lines.join("\n")
}
fn extract_javadoc_comment(lines: &[&str], idx: &mut usize) -> String {
let mut doc_lines = Vec::new();
let mut in_comment = true;
let first = lines[*idx];
let first_trimmed = first.trim();
let start_offset = if first_trimmed.starts_with("/**") {
3
} else {
3
};
let rest = first_trimmed[start_offset..].trim();
if rest.ends_with("*/") {
let content = &rest[..rest.len() - 2].trim();
if !content.is_empty() {
doc_lines.push(content.to_string());
}
*idx += 1;
return doc_lines.join("\n");
}
if !rest.is_empty() {
doc_lines.push(rest.to_string());
}
*idx += 1;
while *idx < lines.len() && in_comment {
let line = lines[*idx].trim();
if line.ends_with("*/") {
let content = &line[..line.len() - 2].trim();
let content = content.trim_start_matches('*').trim();
if !content.is_empty() {
doc_lines.push(content.to_string());
}
in_comment = false;
} else {
let content = line.trim_start_matches('*').trim();
doc_lines.push(content.to_string());
}
*idx += 1;
}
doc_lines.join("\n")
}
fn parse_doc_comment(
&self,
doc_text: &str,
lines: &[&str],
current_idx: usize,
path: &Path,
) -> Option<X86DocComment> {
let mut brief = String::new();
let mut full_text = String::new();
let mut params = Vec::new();
let mut return_desc = None;
let mut exceptions = Vec::new();
let mut see_also = Vec::new();
let mut notes = Vec::new();
let mut warnings = Vec::new();
let mut preconditions = Vec::new();
let mut postconditions = Vec::new();
let mut deprecated = None;
let mut since = None;
let mut authors = Vec::new();
let mut related = Vec::new();
let doc_lines: Vec<&str> = doc_text.lines().collect();
for line in &doc_lines {
let trimmed = line.trim();
if trimmed.starts_with("@param ") || trimmed.starts_with("\\param ") {
let rest = if trimmed.starts_with("@param ") {
&trimmed[7..]
} else {
&trimmed[7..]
};
if let Some((name, desc)) = rest.split_once(' ') {
let direction =
if trimmed.starts_with("[in,out]") || trimmed.starts_with("[in,out]") {
X86DocParamDirection::InOut
} else if name.starts_with("[in]") {
X86DocParamDirection::In
} else if name.starts_with("[out]") {
X86DocParamDirection::Out
} else {
X86DocParamDirection::In
};
let clean_name = name
.trim_start_matches("[in]")
.trim_start_matches("[out]")
.trim_start_matches("[in,out]")
.trim();
params.push(X86DocParam {
name: clean_name.to_string(),
description: desc.trim().to_string(),
direction,
});
}
} else if trimmed.starts_with("@return ") || trimmed.starts_with("\\return ") {
let rest = if trimmed.starts_with("@return ") {
&trimmed[8..]
} else {
&trimmed[8..]
};
return_desc = Some(rest.trim().to_string());
} else if trimmed.starts_with("@returns ") || trimmed.starts_with("\\returns ") {
let rest = if trimmed.starts_with("@returns ") {
&trimmed[9..]
} else {
&trimmed[9..]
};
return_desc = Some(rest.trim().to_string());
} else if trimmed.starts_with("@throws ")
|| trimmed.starts_with("\\throws ")
|| trimmed.starts_with("@exception ")
|| trimmed.starts_with("\\exception ")
{
let (tag, rest) = if trimmed.starts_with('@') {
if let Some(space) = trimmed.find(' ') {
(&trimmed[1..space], &trimmed[space + 1..])
} else {
continue;
}
} else {
if let Some(space) = trimmed.find(' ') {
(&trimmed[1..space], &trimmed[space + 1..])
} else {
continue;
}
};
if let Some((ex_type, desc)) = rest.split_once(' ') {
exceptions.push(X86DocException {
exception_type: ex_type.trim().to_string(),
description: desc.trim().to_string(),
});
}
} else if trimmed.starts_with("@see ") || trimmed.starts_with("\\see ") {
let rest = if trimmed.starts_with("@see ") {
&trimmed[5..]
} else {
&trimmed[5..]
};
see_also.push(rest.trim().to_string());
} else if trimmed.starts_with("@note ") || trimmed.starts_with("\\note ") {
let rest = if trimmed.starts_with("@note ") {
&trimmed[6..]
} else {
&trimmed[6..]
};
notes.push(rest.trim().to_string());
} else if trimmed.starts_with("@warning ") || trimmed.starts_with("\\warning ") {
let rest = if trimmed.starts_with("@warning ") {
&trimmed[9..]
} else {
&trimmed[9..]
};
warnings.push(rest.trim().to_string());
} else if trimmed.starts_with("@pre ") || trimmed.starts_with("\\pre ") {
let rest = if trimmed.starts_with("@pre ") {
&trimmed[5..]
} else {
&trimmed[5..]
};
preconditions.push(rest.trim().to_string());
} else if trimmed.starts_with("@post ") || trimmed.starts_with("\\post ") {
let rest = if trimmed.starts_with("@post ") {
&trimmed[6..]
} else {
&trimmed[6..]
};
postconditions.push(rest.trim().to_string());
} else if trimmed.starts_with("@deprecated ") || trimmed.starts_with("\\deprecated ") {
let rest = if trimmed.starts_with("@deprecated ") {
&trimmed[12..]
} else {
&trimmed[12..]
};
deprecated = Some(rest.trim().to_string());
} else if trimmed.starts_with("@since ") || trimmed.starts_with("\\since ") {
let rest = if trimmed.starts_with("@since ") {
&trimmed[7..]
} else {
&trimmed[7..]
};
since = Some(rest.trim().to_string());
} else if trimmed.starts_with("@author ") || trimmed.starts_with("\\author ") {
let rest = if trimmed.starts_with("@author ") {
&trimmed[8..]
} else {
&trimmed[8..]
};
authors.push(rest.trim().to_string());
} else if trimmed.starts_with("@related ") || trimmed.starts_with("\\related ") {
let rest = if trimmed.starts_with("@related ") {
&trimmed[9..]
} else {
&trimmed[9..]
};
related.push(rest.trim().to_string());
} else if !trimmed.is_empty() {
if brief.is_empty() {
brief = trimmed.to_string();
}
full_text.push_str(trimmed);
full_text.push('\n');
}
}
let symbol_name = if current_idx < lines.len() {
Self::extract_symbol_name(lines[current_idx])
} else {
"unknown".to_string()
};
let symbol_kind = Self::classify_symbol_from_line(if current_idx < lines.len() {
lines[current_idx]
} else {
""
});
Some(X86DocComment {
symbol_name,
symbol_kind,
brief,
full_text: full_text.trim().to_string(),
params,
return_desc,
exceptions,
see_also,
related,
notes,
warnings,
preconditions,
postconditions,
deprecated,
since,
authors,
file_path: path.to_path_buf(),
line: current_idx + 1,
})
}
fn extract_symbol_name(line: &str) -> String {
let trimmed = line.trim();
if trimmed.is_empty() {
return "unknown".to_string();
}
let words: Vec<&str> = trimmed.split_whitespace().collect();
for (i, w) in words.iter().enumerate() {
if matches!(
*w,
"void"
| "int"
| "char"
| "float"
| "double"
| "bool"
| "auto"
| "long"
| "short"
| "unsigned"
| "signed"
| "size_t"
| "ssize_t"
| "uint8_t"
| "uint16_t"
| "uint32_t"
| "uint64_t"
| "int8_t"
| "int16_t"
| "int32_t"
| "int64_t"
| "const"
| "volatile"
| "static"
| "extern"
| "inline"
| "virtual"
| "explicit"
| "mutable"
| "register"
| "class"
| "struct"
| "enum"
| "union"
| "namespace"
| "typedef"
| "using"
| "template"
| "typename"
| "friend"
| "constexpr"
| "consteval"
| "noexcept"
) {
continue;
}
let name = w.trim_end_matches(|c: char| c == '(' || c == '{' || c == ';' || c == ':');
return name.to_string();
}
"unknown".to_string()
}
fn classify_symbol_from_line(line: &str) -> X86DocSymbolKind {
let trimmed = line.trim();
if trimmed.starts_with("class ") {
return X86DocSymbolKind::Class;
}
if trimmed.starts_with("struct ") {
return X86DocSymbolKind::Struct;
}
if trimmed.starts_with("enum class ") {
return X86DocSymbolKind::Enum;
}
if trimmed.starts_with("enum ") {
return X86DocSymbolKind::Enum;
}
if trimmed.starts_with("template<") || trimmed.starts_with("template <") {
return X86DocSymbolKind::Template;
}
if trimmed.starts_with("typedef ") || trimmed.starts_with("using ") {
return X86DocSymbolKind::TypeAlias;
}
if trimmed.starts_with("#define ") {
return X86DocSymbolKind::Macro;
}
if trimmed.starts_with("namespace ") {
return X86DocSymbolKind::Namespace;
}
if trimmed.contains('(') {
return X86DocSymbolKind::Function;
}
X86DocSymbolKind::Variable
}
pub fn generate_markdown(&self) -> String {
let mut md = String::new();
if !self.project_name.is_empty() {
md.push_str(&format!(
"# {} {}\n\n",
self.project_name, self.project_version
));
}
md.push_str("## API Reference\n\n");
for comment in &self.comments {
md.push_str(&format!("### `{}`\n\n", comment.symbol_name));
md.push_str(&format!("*Kind*: {}\n\n", comment.symbol_kind));
if self.include_source_locations {
md.push_str(&format!(
"*Defined in*: `{}` line {}\n\n",
comment.file_path.display(),
comment.line
));
}
if !comment.brief.is_empty() {
md.push_str(&format!("{}\n\n", comment.brief));
}
if !comment.full_text.is_empty() && comment.full_text != comment.brief {
md.push_str(&format!("{}\n\n", comment.full_text));
}
if !comment.params.is_empty() {
md.push_str("#### Parameters\n\n");
md.push_str("| Name | Direction | Description |\n");
md.push_str("|------|-----------|-------------|\n");
for param in &comment.params {
md.push_str(&format!(
"| `{}` | {} | {} |\n",
param.name, param.direction, param.description
));
}
md.push('\n');
}
if let Some(ref ret) = comment.return_desc {
md.push_str("#### Returns\n\n");
md.push_str(&format!("{}\n\n", ret));
}
if !comment.exceptions.is_empty() {
md.push_str("#### Throws\n\n");
for ex in &comment.exceptions {
md.push_str(&format!(
"- **`{}`**: {}\n",
ex.exception_type, ex.description
));
}
md.push('\n');
}
if !comment.warnings.is_empty() {
md.push_str("> **⚠ Warning:**\n");
for w in &comment.warnings {
md.push_str(&format!("> {}\n", w));
}
md.push('\n');
}
if !comment.notes.is_empty() {
md.push_str("> **Note:**\n");
for n in &comment.notes {
md.push_str(&format!("> {}\n", n));
}
md.push('\n');
}
if self.generate_cross_refs && !comment.see_also.is_empty() {
md.push_str("#### See Also\n\n");
for see in &comment.see_also {
md.push_str(&format!("- {}\n", see));
}
md.push('\n');
}
if self.generate_cross_refs && !comment.related.is_empty() {
md.push_str("#### Related\n\n");
for rel in &comment.related {
md.push_str(&format!("- {}\n", rel));
}
md.push('\n');
}
if !comment.preconditions.is_empty() {
md.push_str("#### Preconditions\n\n");
for pre in &comment.preconditions {
md.push_str(&format!("- {}\n", pre));
}
md.push('\n');
}
if !comment.postconditions.is_empty() {
md.push_str("#### Postconditions\n\n");
for post in &comment.postconditions {
md.push_str(&format!("- {}\n", post));
}
md.push('\n');
}
if let Some(ref dep) = comment.deprecated {
md.push_str(&format!("> **⚠ Deprecated:** {}\n\n", dep));
}
if let Some(ref ver) = comment.since {
md.push_str(&format!("*Since*: {}\n\n", ver));
}
md.push_str("---\n\n");
}
md
}
pub fn generate_html(&self) -> String {
let mut html = String::new();
html.push_str("<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n");
html.push_str("<meta charset=\"UTF-8\">\n");
html.push_str(&format!(
"<title>{} {} API Reference</title>\n",
self.project_name, self.project_version
));
html.push_str("<style>\n");
html.push_str("body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; ");
html.push_str("max-width: 960px; margin: 0 auto; padding: 20px; line-height: 1.6; }\n");
html.push_str("h2 { border-bottom: 2px solid #333; padding-bottom: 4px; }\n");
html.push_str("h3 { margin-top: 30px; }\n");
html.push_str(".symbol { font-family: 'Fira Code', monospace; background: #f0f0f0; padding: 2px 6px; border-radius: 3px; }\n");
html.push_str(".kind { color: #666; font-style: italic; }\n");
html.push_str(".location { color: #888; font-size: 0.9em; }\n");
html.push_str("table { border-collapse: collapse; width: 100%; }\n");
html.push_str("th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }\n");
html.push_str("th { background-color: #f5f5f5; }\n");
html.push_str(".warning { background: #fff3cd; border-left: 4px solid #ffc107; padding: 10px; margin: 10px 0; }\n");
html.push_str(".note { background: #d1ecf1; border-left: 4px solid #17a2b8; padding: 10px; margin: 10px 0; }\n");
html.push_str(".deprecated { background: #f8d7da; border-left: 4px solid #dc3545; padding: 10px; margin: 10px 0; }\n");
html.push_str("</style>\n</head>\n<body>\n");
if !self.project_name.is_empty() {
html.push_str(&format!(
"<h1>{} <small>{}</small></h1>\n",
self.project_name, self.project_version
));
}
html.push_str("<h2>API Reference</h2>\n");
for comment in &self.comments {
html.push_str(&format!(
"<h3 id=\"{}\"><span class=\"symbol\">{}</span></h3>\n",
comment.symbol_name, comment.symbol_name
));
html.push_str(&format!(
"<p class=\"kind\">Kind: {}</p>\n",
comment.symbol_kind
));
if self.include_source_locations {
html.push_str(&format!(
"<p class=\"location\">Defined in <code>{}</code> line {}</p>\n",
comment.file_path.display(),
comment.line
));
}
if !comment.brief.is_empty() {
html.push_str(&format!("<p>{}</p>\n", Self::escape_html(&comment.brief)));
}
if !comment.full_text.is_empty() && comment.full_text != comment.brief {
html.push_str(&format!(
"<p>{}</p>\n",
Self::escape_html(&comment.full_text)
));
}
if !comment.params.is_empty() {
html.push_str("<h4>Parameters</h4>\n<table>\n<tr><th>Name</th><th>Direction</th><th>Description</th></tr>\n");
for p in &comment.params {
html.push_str(&format!(
"<tr><td><code>{}</code></td><td>{}</td><td>{}</td></tr>\n",
p.name,
p.direction,
Self::escape_html(&p.description)
));
}
html.push_str("</table>\n");
}
if let Some(ref ret) = comment.return_desc {
html.push_str(&format!(
"<h4>Returns</h4>\n<p>{}</p>\n",
Self::escape_html(ret)
));
}
if !comment.exceptions.is_empty() {
html.push_str("<h4>Throws</h4>\n<ul>\n");
for ex in &comment.exceptions {
html.push_str(&format!(
"<li><code>{}</code>: {}</li>\n",
ex.exception_type,
Self::escape_html(&ex.description)
));
}
html.push_str("</ul>\n");
}
for w in &comment.warnings {
html.push_str(&format!(
"<div class=\"warning\"><strong>⚠ Warning:</strong> {}</div>\n",
Self::escape_html(w)
));
}
for n in &comment.notes {
html.push_str(&format!(
"<div class=\"note\"><strong>Note:</strong> {}</div>\n",
Self::escape_html(n)
));
}
if let Some(ref dep) = comment.deprecated {
html.push_str(&format!(
"<div class=\"deprecated\"><strong>⚠ Deprecated:</strong> {}</div>\n",
Self::escape_html(dep)
));
}
if self.generate_cross_refs && !comment.see_also.is_empty() {
html.push_str("<h4>See Also</h4>\n<ul>\n");
for see in &comment.see_also {
html.push_str(&format!("<li>{}</li>\n", Self::escape_html(see)));
}
html.push_str("</ul>\n");
}
if !comment.related.is_empty() {
html.push_str("<h4>Related</h4>\n<ul>\n");
for rel in &comment.related {
html.push_str(&format!("<li>{}</li>\n", Self::escape_html(rel)));
}
html.push_str("</ul>\n");
}
if let Some(ref ver) = comment.since {
html.push_str(&format!("<p><em>Since: {}</em></p>\n", ver));
}
html.push_str("<hr>\n");
}
html.push_str("</body>\n</html>\n");
html
}
pub fn generate_man_page(&self) -> String {
let mut man = String::new();
let title = if !self.project_name.is_empty() {
self.project_name.clone()
} else {
"API".to_string()
};
man.push_str(&format!(
".TH \"{}\" \"3\" \"\" \"{}\" \"Library Functions Manual\"\n",
title.to_uppercase(),
self.project_version
));
man.push_str(&format!(".SH NAME\n{} \\- API Reference\n", title));
man.push_str(".SH DESCRIPTION\n");
man.push_str("This manual page documents the public API.\n");
for comment in &self.comments {
man.push_str(&format!(".SS {}\n", comment.symbol_name));
man.push_str(&format!("Kind: {}\n.br\n", comment.symbol_kind));
if self.include_source_locations {
man.push_str(&format!(
"Defined in: {} (line {})\n.br\n",
comment.file_path.display(),
comment.line
));
}
if !comment.brief.is_empty() {
man.push_str(&format!("{}\n.br\n", comment.brief));
}
if !comment.params.is_empty() {
man.push_str(".PP\n\\fBParameters:\\fR\n");
for p in &comment.params {
man.push_str(&format!(".IP \\({}\\)\n{}\n", p.name, p.description));
}
}
if let Some(ref ret) = comment.return_desc {
man.push_str(&format!(".PP\n\\fBReturns:\\fR {}\n", ret));
}
if !comment.see_also.is_empty() {
man.push_str(".PP\n\\fBSee Also:\\fR\n");
man.push_str(&comment.see_also.join(", "));
man.push('\n');
}
if let Some(ref dep) = comment.deprecated {
man.push_str(&format!(".PP\n\\fBWARNING:\\fR Deprecated: {}\n", dep));
}
}
man
}
pub fn generate_yaml(&self) -> String {
let mut yaml = String::new();
yaml.push_str("---\n");
if !self.project_name.is_empty() {
yaml.push_str(&format!("project: {}\n", self.project_name));
yaml.push_str(&format!("version: {}\n", self.project_version));
}
yaml.push_str("symbols:\n");
for comment in &self.comments {
yaml.push_str(&format!(" - name: {}\n", comment.symbol_name));
yaml.push_str(&format!(" kind: {}\n", comment.symbol_kind));
yaml.push_str(&format!(" brief: \"{}\"\n", comment.brief));
if !comment.full_text.is_empty() && comment.full_text != comment.brief {
yaml.push_str(&format!(
" description: \"{}\"\n",
comment.full_text.replace('"', "\\\"")
));
}
if self.include_source_locations {
yaml.push_str(&format!(" file: {}\n", comment.file_path.display()));
yaml.push_str(&format!(" line: {}\n", comment.line));
}
if !comment.params.is_empty() {
yaml.push_str(" params:\n");
for p in &comment.params {
yaml.push_str(&format!(" - name: {}\n", p.name));
yaml.push_str(&format!(" direction: {}\n", p.direction));
yaml.push_str(&format!(" description: \"{}\"\n", p.description));
}
}
if let Some(ref ret) = comment.return_desc {
yaml.push_str(&format!(" return: \"{}\"\n", ret));
}
if !comment.exceptions.is_empty() {
yaml.push_str(" exceptions:\n");
for ex in &comment.exceptions {
yaml.push_str(&format!(" - type: {}\n", ex.exception_type));
yaml.push_str(&format!(" description: \"{}\"\n", ex.description));
}
}
if !comment.see_also.is_empty() {
yaml.push_str(" see_also:\n");
for s in &comment.see_also {
yaml.push_str(&format!(" - {}\n", s));
}
}
if !comment.related.is_empty() {
yaml.push_str(" related:\n");
for r in &comment.related {
yaml.push_str(&format!(" - {}\n", r));
}
}
if let Some(ref dep) = comment.deprecated {
yaml.push_str(&format!(" deprecated: \"{}\"\n", dep));
}
if let Some(ref ver) = comment.since {
yaml.push_str(&format!(" since: \"{}\"\n", ver));
}
if !comment.authors.is_empty() {
yaml.push_str(" authors:\n");
for a in &comment.authors {
yaml.push_str(&format!(" - {}\n", a));
}
}
if !comment.warnings.is_empty() {
yaml.push_str(" warnings:\n");
for w in &comment.warnings {
yaml.push_str(&format!(" - \"{}\"\n", w));
}
}
if !comment.notes.is_empty() {
yaml.push_str(" notes:\n");
for n in &comment.notes {
yaml.push_str(&format!(" - \"{}\"\n", n));
}
}
if !comment.preconditions.is_empty() {
yaml.push_str(" preconditions:\n");
for pre in &comment.preconditions {
yaml.push_str(&format!(" - \"{}\"\n", pre));
}
}
if !comment.postconditions.is_empty() {
yaml.push_str(" postconditions:\n");
for post in &comment.postconditions {
yaml.push_str(&format!(" - \"{}\"\n", post));
}
}
}
yaml
}
pub fn generate_json(&self) -> String {
let mut json = String::new();
json.push_str("{\n");
if !self.project_name.is_empty() {
json.push_str(&format!(" \"project\": \"{}\",\n", self.project_name));
json.push_str(&format!(" \"version\": \"{}\",\n", self.project_version));
}
json.push_str(" \"symbols\": [\n");
for (idx, comment) in self.comments.iter().enumerate() {
json.push_str(" {\n");
json.push_str(&format!(" \"name\": \"{}\",\n", comment.symbol_name));
json.push_str(&format!(" \"kind\": \"{}\",\n", comment.symbol_kind));
json.push_str(&format!(" \"brief\": \"{}\",\n", comment.brief));
if self.include_source_locations {
json.push_str(&format!(
" \"file\": \"{}\",\n",
comment
.file_path
.display()
.to_string()
.replace('\\', "\\\\")
));
json.push_str(&format!(" \"line\": {},\n", comment.line));
}
if !comment.params.is_empty() {
json.push_str(" \"params\": [\n");
for (pi, p) in comment.params.iter().enumerate() {
json.push_str(" {\n");
json.push_str(&format!(" \"name\": \"{}\",\n", p.name));
json.push_str(&format!(" \"direction\": \"{}\",\n", p.direction));
json.push_str(&format!(
" \"description\": \"{}\"\n",
p.description.replace('"', "\\\"")
));
json.push_str(" }");
if pi + 1 < comment.params.len() {
json.push(',');
}
json.push('\n');
}
json.push_str(" ],\n");
}
if let Some(ref ret) = comment.return_desc {
json.push_str(&format!(
" \"return\": \"{}\",\n",
ret.replace('"', "\\\"")
));
}
if let Some(ref dep) = comment.deprecated {
json.push_str(&format!(
" \"deprecated\": \"{}\",\n",
dep.replace('"', "\\\"")
));
}
if let Some(ref ver) = comment.since {
json.push_str(&format!(" \"since\": \"{}\",\n", ver));
}
json.push_str(&format!(
" \"description\": \"{}\"\n",
comment.full_text.replace('"', "\\\"")
));
json.push_str(" }");
if idx + 1 < self.comments.len() {
json.push(',');
}
json.push('\n');
}
json.push_str(" ]\n");
json.push_str("}\n");
json
}
pub fn generate(&self, format: X86DocOutputFormat) -> String {
match format {
X86DocOutputFormat::Markdown => self.generate_markdown(),
X86DocOutputFormat::HTML => self.generate_html(),
X86DocOutputFormat::ManPage => self.generate_man_page(),
X86DocOutputFormat::YAML => self.generate_yaml(),
X86DocOutputFormat::JSON => self.generate_json(),
X86DocOutputFormat::RST => self.generate_rst(),
X86DocOutputFormat::PlainText => self.generate_plain_text(),
}
}
fn generate_rst(&self) -> String {
let mut rst = String::new();
if !self.project_name.is_empty() {
rst.push_str(&format!("{}\n", "=".repeat(self.project_name.len())));
rst.push_str(&format!("{}\n", self.project_name));
rst.push_str(&format!("{}\n\n", "=".repeat(self.project_name.len())));
}
for comment in &self.comments {
rst.push_str(&format!(".. _{}:\n\n", comment.symbol_name));
rst.push_str(&format!(
"{}\n{}\n\n",
comment.symbol_name,
"-".repeat(comment.symbol_name.len())
));
rst.push_str(&format!("**Kind**: {}\n\n", comment.symbol_kind));
if !comment.brief.is_empty() {
rst.push_str(&format!("{}\n\n", comment.brief));
}
if !comment.params.is_empty() {
rst.push_str(":Parameters:\n");
for p in &comment.params {
rst.push_str(&format!(
" * **{}** (*{}*) — {}\n",
p.name, p.direction, p.description
));
}
rst.push('\n');
}
if let Some(ref ret) = comment.return_desc {
rst.push_str(&format!(":Returns: {}\n\n", ret));
}
if !comment.see_also.is_empty() {
rst.push_str(":See Also:\n");
for s in &comment.see_also {
rst.push_str(&format!(" * {}\n", s));
}
rst.push('\n');
}
rst.push('\n');
}
rst
}
fn generate_plain_text(&self) -> String {
let mut txt = String::new();
if !self.project_name.is_empty() {
txt.push_str(&format!("{} {}\n", self.project_name, self.project_version));
txt.push_str(&format!(
"{}\n\n",
"=".repeat(self.project_name.len() + self.project_version.len() + 1)
));
}
for comment in &self.comments {
txt.push_str(&format!("{}\n", comment.symbol_name));
txt.push_str(&format!("{}\n", "-".repeat(comment.symbol_name.len())));
txt.push_str(&format!("Kind: {}\n", comment.symbol_kind));
if self.include_source_locations {
txt.push_str(&format!(
"File: {} (line {})\n",
comment.file_path.display(),
comment.line
));
}
if !comment.brief.is_empty() {
txt.push_str(&format!("\n{}\n", comment.brief));
}
if !comment.params.is_empty() {
txt.push_str("\nParameters:\n");
for p in &comment.params {
txt.push_str(&format!(
" {} [{}]: {}\n",
p.name, p.direction, p.description
));
}
}
if let Some(ref ret) = comment.return_desc {
txt.push_str(&format!("\nReturns: {}\n", ret));
}
if !comment.see_also.is_empty() {
txt.push_str("\nSee Also:\n");
for s in &comment.see_also {
txt.push_str(&format!(" - {}\n", s));
}
}
txt.push_str("\n\n");
}
txt
}
pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), String> {
let path = path.as_ref();
let output = self.generate(self.output_format);
let dir = path.parent().unwrap_or(Path::new("."));
fs::create_dir_all(dir).map_err(|e| format!("Cannot create directory: {}", e))?;
fs::write(path, output).map_err(|e| format!("Cannot write '{}': {}", path.display(), e))?;
Ok(())
}
fn escape_html(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
}
pub fn build_cross_reference_index(&self) -> BTreeMap<String, Vec<String>> {
let mut index: BTreeMap<String, Vec<String>> = BTreeMap::new();
for comment in &self.comments {
for see in &comment.see_also {
let entry = index.entry(see.clone()).or_default();
entry.push(comment.symbol_name.clone());
}
for rel in &comment.related {
let entry = index.entry(rel.clone()).or_default();
entry.push(comment.symbol_name.clone());
}
let entry = index.entry(comment.symbol_name.clone()).or_default();
if entry.is_empty() {
entry.push("(self)".to_string());
}
}
index
}
}
impl Default for X86ClangDoc {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86IncludeMapping {
pub symbol: String,
pub header: String,
pub is_system: bool,
pub language: X86IncludeLanguage,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86IncludeLanguage {
C,
Cpp,
Both,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86IncludeSortStyle {
LLVM,
Google,
Custom,
None,
}
#[derive(Debug, Clone)]
pub struct X86IncludeFixResult {
pub file: PathBuf,
pub missing_includes: Vec<String>,
pub unused_includes: Vec<String>,
pub sorted: bool,
pub patches_applied: usize,
}
#[derive(Debug, Clone)]
pub struct X86ClangIncludeFixer {
pub enabled: bool,
pub include_mappings: Vec<X86IncludeMapping>,
pub sort_style: X86IncludeSortStyle,
pub custom_order: Vec<String>,
pub remove_unused: bool,
pub add_missing: bool,
pub sort_includes: bool,
pub verbose: bool,
}
impl X86ClangIncludeFixer {
pub fn new() -> Self {
let mappings = Self::default_mappings();
Self {
enabled: true,
include_mappings: mappings,
sort_style: X86IncludeSortStyle::LLVM,
custom_order: Vec::new(),
remove_unused: true,
add_missing: true,
sort_includes: true,
verbose: false,
}
}
fn default_mappings() -> Vec<X86IncludeMapping> {
let mut m = Vec::new();
let c_mappings = [
("printf", "stdio.h"),
("fprintf", "stdio.h"),
("sprintf", "stdio.h"),
("scanf", "stdio.h"),
("fopen", "stdio.h"),
("fclose", "stdio.h"),
("fread", "stdio.h"),
("fwrite", "stdio.h"),
("fgets", "stdio.h"),
("malloc", "stdlib.h"),
("free", "stdlib.h"),
("calloc", "stdlib.h"),
("realloc", "stdlib.h"),
("exit", "stdlib.h"),
("atoi", "stdlib.h"),
("strlen", "string.h"),
("strcpy", "string.h"),
("strcmp", "string.h"),
("strcat", "string.h"),
("memcpy", "string.h"),
("memmove", "string.h"),
("memset", "string.h"),
("strdup", "string.h"),
("strstr", "string.h"),
("assert", "assert.h"),
("sin", "math.h"),
("cos", "math.h"),
("sqrt", "math.h"),
("pow", "math.h"),
("abs", "stdlib.h"),
("time", "time.h"),
("clock", "time.h"),
("pthread_create", "pthread.h"),
("pthread_mutex_lock", "pthread.h"),
];
for (sym, hdr) in &c_mappings {
m.push(X86IncludeMapping {
symbol: sym.to_string(),
header: hdr.to_string(),
is_system: true,
language: X86IncludeLanguage::C,
});
}
let cpp_mappings = [
("std::string", "string"),
("std::vector", "vector"),
("std::map", "map"),
("std::set", "set"),
("std::unordered_map", "unordered_map"),
("std::unordered_set", "unordered_set"),
("std::list", "list"),
("std::deque", "deque"),
("std::queue", "queue"),
("std::stack", "stack"),
("std::shared_ptr", "memory"),
("std::unique_ptr", "memory"),
("std::make_shared", "memory"),
("std::make_unique", "memory"),
("std::cout", "iostream"),
("std::cin", "iostream"),
("std::cerr", "iostream"),
("std::endl", "ostream"),
("std::sort", "algorithm"),
("std::find", "algorithm"),
("std::copy", "algorithm"),
("std::transform", "algorithm"),
("std::move", "utility"),
("std::forward", "utility"),
("std::pair", "utility"),
("std::tuple", "tuple"),
("std::function", "functional"),
("std::bind", "functional"),
("std::thread", "thread"),
("std::mutex", "mutex"),
("std::lock_guard", "mutex"),
("std::unique_lock", "mutex"),
("std::regex", "regex"),
("std::smatch", "regex"),
("std::chrono", "chrono"),
("std::filesystem", "filesystem"),
("std::optional", "optional"),
("std::variant", "variant"),
("std::any", "any"),
("std::string_view", "string_view"),
("std::span", "span"),
];
for (sym, hdr) in &cpp_mappings {
m.push(X86IncludeMapping {
symbol: sym.to_string(),
header: hdr.to_string(),
is_system: true,
language: X86IncludeLanguage::Cpp,
});
}
m
}
pub fn find_missing_includes(&self, source: &str) -> Vec<String> {
let mut missing = Vec::new();
let mut seen_headers = HashSet::new();
for line in source.lines() {
if let Some(header) = Self::extract_include_header(line) {
seen_headers.insert(header);
}
}
for mapping in &self.include_mappings {
if seen_headers.contains(&mapping.header) {
continue;
}
if source.contains(&mapping.symbol) {
if !missing.contains(&mapping.header) {
missing.push(mapping.header.clone());
}
}
}
missing.sort();
missing
}
pub fn find_unused_includes(&self, source: &str) -> Vec<String> {
let mut unused = Vec::new();
for line in source.lines() {
let trimmed = line.trim();
if !trimmed.starts_with("#include") {
continue;
}
if let Some(header) = Self::extract_include_header(trimmed) {
let is_used = self
.include_mappings
.iter()
.any(|m| m.header == header && source.contains(&m.symbol));
let is_directly_used = {
let base = header.rsplit('/').next().unwrap_or(&header);
let base_no_ext = base.split('.').next().unwrap_or(base);
source.contains(base_no_ext)
};
if !is_used && !is_directly_used && trimmed.contains('"') {
unused.push(trimmed.to_string());
}
}
}
unused
}
pub fn extract_include_header(line: &str) -> Option<String> {
let trimmed = line.trim();
if !trimmed.starts_with("#include") {
return None;
}
if let (Some(start), Some(end)) = (trimmed.find('<'), trimmed.find('>')) {
Some(trimmed[start + 1..end].to_string())
} else if let (Some(_), Some(_)) = (
trimmed.find('"'),
trimmed[trimmed.find('"')? + 1..].find('"'),
) {
let real_start = trimmed.find('"')? + 1;
let real_end = real_start + trimmed[real_start..].find('"')?;
Some(trimmed[real_start..real_end].to_string())
} else {
None
}
}
pub fn sort_include_lines(&self, lines: &[String]) -> Vec<String> {
match self.sort_style {
X86IncludeSortStyle::None => lines.to_vec(),
X86IncludeSortStyle::LLVM => self.sort_llvm_style(lines),
X86IncludeSortStyle::Google => self.sort_google_style(lines),
X86IncludeSortStyle::Custom => self.sort_custom_style(lines),
}
}
fn sort_llvm_style(&self, lines: &[String]) -> Vec<String> {
let mut main_header: Vec<String> = Vec::new();
let mut local_quotes: Vec<String> = Vec::new();
let mut project_quotes: Vec<String> = Vec::new();
let mut system_angles: Vec<String> = Vec::new();
let mut unknown: Vec<String> = Vec::new();
for line in lines {
let trimmed = line.trim();
if trimmed.contains('"') {
let header = Self::extract_include_header(trimmed).unwrap_or_default();
if header.contains('/') || header.contains("..") {
project_quotes.push(line.clone());
} else {
local_quotes.push(line.clone());
}
} else if trimmed.contains('<') && trimmed.contains('>') {
system_angles.push(line.clone());
} else {
unknown.push(line.clone());
}
}
local_quotes.sort_by(|a, b| {
let ha = Self::extract_include_header(a).unwrap_or_default();
let hb = Self::extract_include_header(b).unwrap_or_default();
ha.to_lowercase().cmp(&hb.to_lowercase())
});
project_quotes.sort_by(|a, b| {
let ha = Self::extract_include_header(a).unwrap_or_default();
let hb = Self::extract_include_header(b).unwrap_or_default();
ha.to_lowercase().cmp(&hb.to_lowercase())
});
system_angles.sort_by(|a, b| {
let ha = Self::extract_include_header(a).unwrap_or_default();
let hb = Self::extract_include_header(b).unwrap_or_default();
ha.to_lowercase().cmp(&hb.to_lowercase())
});
let mut result = Vec::new();
result.extend(main_header);
if !local_quotes.is_empty() {
result.extend(local_quotes);
result.push(String::new());
}
if !project_quotes.is_empty() {
result.extend(project_quotes);
result.push(String::new());
}
if !system_angles.is_empty() {
result.extend(system_angles);
}
result.extend(unknown);
result
}
fn sort_google_style(&self, lines: &[String]) -> Vec<String> {
let mut related: Vec<String> = Vec::new();
let mut c_system: Vec<String> = Vec::new();
let mut cpp_system: Vec<String> = Vec::new();
let mut other_libs: Vec<String> = Vec::new();
let mut project: Vec<String> = Vec::new();
for line in lines {
let trimmed = line.trim();
if let Some(header) = Self::extract_include_header(trimmed) {
if trimmed.contains('"') {
project.push(line.clone());
} else if header.ends_with(".h") {
c_system.push(line.clone());
} else {
cpp_system.push(line.clone());
}
} else {
other_libs.push(line.clone());
}
}
let sort_fn = |a: &String, b: &String| {
let ha = Self::extract_include_header(a).unwrap_or_default();
let hb = Self::extract_include_header(b).unwrap_or_default();
ha.to_lowercase().cmp(&hb.to_lowercase())
};
related.sort_by(&sort_fn);
c_system.sort_by(&sort_fn);
cpp_system.sort_by(&sort_fn);
other_libs.sort_by(&sort_fn);
project.sort_by(&sort_fn);
let mut result = Vec::new();
result.extend(related);
if !c_system.is_empty() {
result.extend(c_system);
result.push(String::new());
}
if !cpp_system.is_empty() {
result.extend(cpp_system);
result.push(String::new());
}
if !other_libs.is_empty() {
result.extend(other_libs);
result.push(String::new());
}
result.extend(project);
result
}
fn sort_custom_style(&self, lines: &[String]) -> Vec<String> {
let mut sorted = lines.to_vec();
sorted.sort_by(|a, b| {
let prio_a = self.custom_priority(a);
let prio_b = self.custom_priority(b);
prio_a.cmp(&prio_b).then_with(|| {
let ha = Self::extract_include_header(a).unwrap_or_default();
let hb = Self::extract_include_header(b).unwrap_or_default();
ha.to_lowercase().cmp(&hb.to_lowercase())
})
});
sorted
}
fn custom_priority(&self, line: &str) -> usize {
let header = Self::extract_include_header(line)
.unwrap_or_default()
.to_lowercase();
for (i, pattern) in self.custom_order.iter().enumerate() {
if header.contains(&pattern.to_lowercase()) {
return i;
}
}
self.custom_order.len()
}
pub fn fix_file<P: AsRef<Path>>(&self, path: P) -> Result<usize, String> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.map_err(|e| format!("Cannot read '{}': {}", path.display(), e))?;
let (fixed, patches) = self.fix_source(&content);
if patches > 0 {
fs::write(path, &fixed)
.map_err(|e| format!("Cannot write '{}': {}", path.display(), e))?;
}
Ok(patches)
}
pub fn fix_source(&self, source: &str) -> (String, usize) {
let mut result = source.to_string();
let mut patches = 0;
if self.add_missing {
let missing = self.find_missing_includes(source);
if !missing.is_empty() {
let mut insertions = String::new();
for hdr in &missing {
if hdr.contains(".h")
|| hdr == "assert.h"
|| hdr == "math.h"
|| hdr == "stdlib.h"
|| hdr == "stdio.h"
|| hdr == "string.h"
{
insertions.push_str(&format!("#include <{}>\n", hdr));
} else {
insertions.push_str(&format!("#include <{}>\n", hdr));
}
patches += 1;
}
if let Some(last_include_pos) = result.rfind("#include") {
if let Some(line_end) = result[last_include_pos..].find('\n') {
let insert_pos = last_include_pos + line_end + 1;
result.insert_str(insert_pos, &insertions);
}
}
}
}
if self.remove_unused {
let unused = self.find_unused_includes(source);
for uns in &unused {
if result.contains(uns) {
let to_remove = format!("{}\n", uns);
result = result.replace(&to_remove, "");
patches += 1;
}
}
}
if self.sort_includes {
let lines: Vec<String> = result.lines().map(|l| l.to_string()).collect();
let mut include_lines: Vec<String> = Vec::new();
let mut non_include_lines: Vec<String> = Vec::new();
let mut in_include_block = true;
for line in &lines {
let trimmed = line.trim();
if trimmed.starts_with("#include") {
include_lines.push(line.clone());
} else if in_include_block && (trimmed.is_empty() || trimmed.starts_with("#pragma"))
{
include_lines.push(line.clone());
} else {
in_include_block = false;
non_include_lines.push(line.clone());
}
}
let sorted_includes = self.sort_include_lines(&include_lines);
let mut new_result: Vec<String> = Vec::new();
new_result.extend(sorted_includes);
new_result.extend(non_include_lines);
result = new_result.join("\n");
patches += 1;
}
(result, patches)
}
pub fn process_file<P: AsRef<Path>>(&self, _path: P) -> Result<X86IncludeFixResult, String> {
let path = _path.as_ref();
let source = fs::read_to_string(path)
.map_err(|e| format!("Cannot read '{}': {}", path.display(), e))?;
let missing = self.find_missing_includes(&source);
let unused = self.find_unused_includes(&source);
let sorted = self.sort_includes;
Ok(X86IncludeFixResult {
file: path.to_path_buf(),
missing_includes: missing,
unused_includes: unused,
sorted,
patches_applied: 0,
})
}
}
impl Default for X86ClangIncludeFixer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86ClangMoveOperation {
pub symbol_name: String,
pub symbol_kind: X86DocSymbolKind,
pub source_file: PathBuf,
pub target_file: PathBuf,
pub source_range: X86SourceRange,
pub update_callers: bool,
pub add_forward_decl: bool,
pub add_missing_includes: bool,
}
#[derive(Debug, Clone)]
pub struct X86ClangMoveResult {
pub operation: X86ClangMoveOperation,
pub callers_updated: usize,
pub includes_added: usize,
pub success: bool,
pub error: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86ClangMove {
pub operations: Vec<X86ClangMoveOperation>,
pub dry_run: bool,
pub source_cache: HashMap<PathBuf, String>,
pub verbose: bool,
}
impl X86ClangMove {
pub fn new() -> Self {
Self {
operations: Vec::new(),
dry_run: false,
source_cache: HashMap::new(),
verbose: false,
}
}
pub fn add_move(&mut self, operation: X86ClangMoveOperation) {
self.operations.push(operation);
}
pub fn move_function(&mut self, func_name: &str, source_file: PathBuf, target_file: PathBuf) {
self.operations.push(X86ClangMoveOperation {
symbol_name: func_name.to_string(),
symbol_kind: X86DocSymbolKind::Function,
source_file,
target_file,
source_range: X86SourceRange::new(1, 1, 1, 1),
update_callers: true,
add_forward_decl: true,
add_missing_includes: true,
});
}
pub fn move_class(&mut self, class_name: &str, source_file: PathBuf, target_file: PathBuf) {
self.operations.push(X86ClangMoveOperation {
symbol_name: class_name.to_string(),
symbol_kind: X86DocSymbolKind::Class,
source_file,
target_file,
source_range: X86SourceRange::new(1, 1, 1, 1),
update_callers: true,
add_forward_decl: true,
add_missing_includes: true,
});
}
pub fn find_definition_range(&self, source: &str, symbol_name: &str) -> Option<X86SourceRange> {
let lines: Vec<&str> = source.lines().collect();
for (idx, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.contains(symbol_name)
&& (trimmed.contains('{') || trimmed.ends_with('{'))
&& !trimmed.starts_with("//")
&& !trimmed.starts_with("/*")
{
let mut depth = 0;
let mut found_open = false;
let start_line = idx + 1;
let mut end_line = start_line;
for (j, l) in lines[idx..].iter().enumerate() {
for ch in l.chars() {
if ch == '{' {
depth += 1;
found_open = true;
}
if ch == '}' {
depth -= 1;
}
}
if found_open && depth == 0 {
end_line = idx + j + 1;
break;
}
}
if found_open {
return Some(X86SourceRange::new(
start_line,
1,
end_line,
lines.get(end_line - 1).map(|l| l.len()).unwrap_or(1),
));
}
}
}
None
}
pub fn find_callers(&self, source: &str, symbol_name: &str) -> Vec<X86SourceRange> {
let mut callers = Vec::new();
let lines: Vec<&str> = source.lines().collect();
for (idx, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") || trimmed.starts_with('*') {
continue;
}
if trimmed.contains(symbol_name) {
if !trimmed.contains('{') && !trimmed.ends_with('{') {
let next_is_brace =
lines.get(idx + 1).map(|l| l.trim() == "{").unwrap_or(false);
if !next_is_brace {
callers.push(X86SourceRange::new(idx + 1, 1, idx + 1, line.len()));
}
}
}
}
callers
}
pub fn extract_definition(&self, source: &str, range: &X86SourceRange) -> String {
let lines: Vec<&str> = source.lines().collect();
let start = range.start_line.saturating_sub(1);
let end = range.end_line.min(lines.len());
if start >= lines.len() {
return String::new();
}
lines[start..end].join("\n")
}
pub fn extract_dependencies(&self, source: &str, _symbol_name: &str) -> Vec<String> {
let mut deps = Vec::new();
for line in source.lines() {
let trimmed = line.trim();
if trimmed.starts_with("#include") {
deps.push(trimmed.to_string());
} else if !trimmed.is_empty() && !trimmed.starts_with("//") {
break; }
}
deps
}
pub fn execute_all(&mut self) -> Vec<X86ClangMoveResult> {
let mut results = Vec::new();
for op in self.operations.clone() {
let result = self.execute_move(&op);
results.push(result);
}
results
}
pub fn execute_move(&mut self, op: &X86ClangMoveOperation) -> X86ClangMoveResult {
let mut result = X86ClangMoveResult {
operation: op.clone(),
callers_updated: 0,
includes_added: 0,
success: false,
error: None,
};
let source_content = match fs::read_to_string(&op.source_file) {
Ok(c) => c,
Err(e) => {
result.error = Some(format!("Cannot read source: {}", e));
return result;
}
};
let def_range = match self.find_definition_range(&source_content, &op.symbol_name) {
Some(r) => r,
None => {
result.error = Some(format!(
"Cannot find definition of '{}' in {}",
op.symbol_name,
op.source_file.display()
));
return result;
}
};
let definition = self.extract_definition(&source_content, &def_range);
if self.dry_run {
result.success = true;
return result;
}
let deps = self.extract_dependencies(&source_content, &op.symbol_name);
let target_content = fs::read_to_string(&op.target_file).unwrap_or_default();
let mut new_includes = String::new();
for dep in &deps {
if !target_content.contains(dep) {
new_includes.push_str(dep);
new_includes.push('\n');
result.includes_added += 1;
}
}
let mut new_target = target_content;
if !new_includes.is_empty() {
let insert_pos = new_target
.rfind("#include")
.and_then(|p| new_target[p..].find('\n'))
.map(|n| p_for_target(&new_target) + n + 1)
.unwrap_or(0);
if insert_pos > 0 {
new_target.insert_str(insert_pos, &new_includes);
}
}
new_target.push_str("\n// Moved from: ");
new_target.push_str(&op.source_file.display().to_string());
new_target.push('\n');
new_target.push_str(&definition);
new_target.push('\n');
if let Err(e) = fs::write(&op.target_file, &new_target) {
result.error = Some(format!("Cannot write target: {}", e));
return result;
}
if op.update_callers {
for (path, content) in &self.source_cache.clone() {
let callers = self.find_callers(content, &op.symbol_name);
if !callers.is_empty() {
result.callers_updated += callers.len();
let target_name = op
.target_file
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown");
let include_line = format!("#include \"{}.h\"", target_name);
if !content.contains(&include_line) {
let mut updated = content.clone();
if let Some(pos) = updated
.rfind("#include")
.and_then(|p| updated[p..].find('\n'))
.map(|n| n + 1)
{
updated.insert_str(pos, &format!("{}\n", include_line));
}
self.source_cache.insert(path.clone(), updated);
}
}
}
}
if op.add_forward_decl {
let lines: Vec<&str> = source_content.lines().collect();
let start = def_range.start_line.saturating_sub(1);
let end = def_range.end_line.min(lines.len());
let before = lines[..start].join("\n");
let after = if end < lines.len() {
lines[end..].join("\n")
} else {
String::new()
};
let fwd_decl = self.generate_forward_decl(&definition, &op.symbol_name);
let modified_source = format!("{}\n{}\n{}", before, fwd_decl, after);
self.source_cache
.insert(op.source_file.clone(), modified_source);
}
result.success = true;
result
}
fn generate_forward_decl(&self, definition: &str, _symbol_name: &str) -> String {
let first_line = definition.lines().next().unwrap_or("");
let trimmed = first_line.trim();
if trimmed.contains('(') {
let sig = trimmed.trim_end_matches('{').trim();
if sig.ends_with(')') {
format!("{};", sig)
} else {
format!("// Forward declaration for: {}\n{};", _symbol_name, sig)
}
} else if trimmed.starts_with("class ") || trimmed.starts_with("struct ") {
format!("{};", trimmed.trim_end_matches('{').trim())
} else {
format!("// Forward declaration for: {}", _symbol_name)
}
}
pub fn process_file<P: AsRef<Path>>(&self, path: P) -> Result<usize, String> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.map_err(|e| format!("Cannot read '{}': {}", path.display(), e))?;
let count = self
.operations
.iter()
.filter(|op| op.source_file == path || op.target_file == path)
.count();
Ok(count)
}
pub fn load_source(&mut self, path: &Path) -> Result<String, String> {
if let Some(content) = self.source_cache.get(path) {
return Ok(content.clone());
}
let content = fs::read_to_string(path)
.map_err(|e| format!("Cannot read '{}': {}", path.display(), e))?;
self.source_cache
.insert(path.to_path_buf(), content.clone());
Ok(content)
}
}
fn p_for_target(s: &str) -> usize {
let pos = s.rfind("#include");
if let Some(p) = pos {
if let Some(nl) = s[p..].find('\n') {
return p + nl + 1;
}
}
0
}
impl Default for X86ClangMove {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86RenameOperation {
pub old_name: String,
pub new_name: String,
pub symbol_kind: X86DocSymbolKind,
pub file_paths: Vec<PathBuf>,
pub is_macro: bool,
pub handle_overloads: bool,
pub handle_template_specs: bool,
pub qualification: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86SymbolReference {
pub symbol_name: String,
pub file_path: PathBuf,
pub range: X86SourceRange,
pub ref_type: X86SymbolRefType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86SymbolRefType {
Definition,
Declaration,
Call,
Reference,
TemplateSpecialization,
MacroExpansion,
IncludeDirective,
}
#[derive(Debug, Clone)]
pub struct X86RenameResult {
pub old_name: String,
pub new_name: String,
pub files_changed: usize,
pub references_updated: usize,
pub error: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86ClangRename {
pub operations: Vec<X86RenameOperation>,
pub dry_run: bool,
pub source_cache: HashMap<PathBuf, String>,
pub verbose: bool,
}
impl X86ClangRename {
pub fn new() -> Self {
Self {
operations: Vec::new(),
dry_run: false,
source_cache: HashMap::new(),
verbose: false,
}
}
pub fn add_rename(
&mut self,
old_name: impl Into<String>,
new_name: impl Into<String>,
symbol_kind: X86DocSymbolKind,
file_paths: Vec<PathBuf>,
) {
self.operations.push(X86RenameOperation {
old_name: old_name.into(),
new_name: new_name.into(),
symbol_kind,
file_paths,
is_macro: false,
handle_overloads: true,
handle_template_specs: true,
qualification: None,
});
}
pub fn add_macro_rename(
&mut self,
old_name: impl Into<String>,
new_name: impl Into<String>,
file_paths: Vec<PathBuf>,
) {
self.operations.push(X86RenameOperation {
old_name: old_name.into(),
new_name: new_name.into(),
symbol_kind: X86DocSymbolKind::Macro,
file_paths,
is_macro: true,
handle_overloads: false,
handle_template_specs: false,
qualification: None,
});
}
pub fn find_references(
&self,
source: &str,
symbol_name: &str,
file_path: &Path,
) -> Vec<X86SymbolReference> {
let mut refs = Vec::new();
let lines: Vec<&str> = source.lines().collect();
for (idx, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") || trimmed.starts_with('*') {
continue;
}
if self.contains_symbol(trimmed, symbol_name) {
let ref_type = Self::classify_reference(trimmed, symbol_name);
refs.push(X86SymbolReference {
symbol_name: symbol_name.to_string(),
file_path: file_path.to_path_buf(),
range: X86SourceRange::new(idx + 1, 1, idx + 1, line.len()),
ref_type,
});
}
}
refs
}
fn contains_symbol(&self, line: &str, symbol: &str) -> bool {
if !line.contains(symbol) {
return false;
}
for (pos, _) in line.match_indices(symbol) {
let before = if pos > 0 {
line.as_bytes().get(pos - 1).map(|&b| b as char)
} else {
None
};
let after = line.as_bytes().get(pos + symbol.len()).map(|&b| b as char);
let is_word_boundary = |c: Option<char>| -> bool {
match c {
None => true,
Some(ch) => !ch.is_alphanumeric() && ch != '_',
}
};
if is_word_boundary(before) && is_word_boundary(after) {
return true;
}
}
false
}
fn classify_reference(line: &str, symbol: &str) -> X86SymbolRefType {
let trimmed = line.trim();
if trimmed.starts_with("#define ") {
return X86SymbolRefType::Definition;
}
if trimmed.starts_with("#include") && trimmed.contains(symbol) {
return X86SymbolRefType::IncludeDirective;
}
if trimmed.starts_with(&format!("template<>")) {
return X86SymbolRefType::TemplateSpecialization;
}
if trimmed.contains('{') || trimmed.ends_with('{') {
return X86SymbolRefType::Definition;
}
if trimmed.ends_with(';') && !trimmed.contains('(') {
return X86SymbolRefType::Declaration;
}
if trimmed.contains(&format!("{}(&", symbol)) || trimmed.contains(&format!("{}(", symbol)) {
return X86SymbolRefType::Call;
}
X86SymbolRefType::Reference
}
pub fn execute_all(&mut self) -> Vec<X86RenameResult> {
let mut results = Vec::new();
for op in self.operations.clone() {
let result = self.execute_rename(&op);
results.push(result);
}
results
}
pub fn execute_rename(&mut self, op: &X86RenameOperation) -> X86RenameResult {
let mut result = X86RenameResult {
old_name: op.old_name.clone(),
new_name: op.new_name.clone(),
files_changed: 0,
references_updated: 0,
error: None,
};
for file_path in &op.file_paths {
let content = match fs::read_to_string(file_path) {
Ok(c) => c,
Err(e) => {
if self.verbose {
eprintln!("Cannot read {}: {}", file_path.display(), e);
}
continue;
}
};
let refs = self.find_references(&content, &op.old_name, file_path);
if refs.is_empty() {
continue;
}
result.references_updated += refs.len();
if self.dry_run {
continue;
}
let original = content.clone();
let mut modified = content;
if op.is_macro {
modified = modified.replace(&op.old_name, &op.new_name);
} else {
modified = self.replace_symbol_in_source(&modified, &op.old_name, &op.new_name);
}
if modified != original {
if let Err(e) = fs::write(file_path, &modified) {
result.error = Some(format!("Cannot write {}: {}", file_path.display(), e));
} else {
result.files_changed += 1;
}
}
}
result
}
fn replace_symbol_in_source(&self, source: &str, old: &str, new: &str) -> String {
let mut result =
String::with_capacity(source.len() + (new.len().saturating_sub(old.len())) * 10);
let bytes = source.as_bytes();
let old_bytes = old.as_bytes();
let mut i = 0;
while i < bytes.len() {
if i + old_bytes.len() <= bytes.len() && &bytes[i..i + old_bytes.len()] == old_bytes {
let before = if i > 0 {
Some(bytes[i - 1] as char)
} else {
None
};
let after = bytes.get(i + old_bytes.len()).map(|&b| b as char);
let is_boundary = |c: Option<char>| -> bool {
match c {
None => true,
Some(ch) => !ch.is_alphanumeric() && ch != '_',
}
};
let is_inside_line_comment = {
let before_text = &source[..i];
before_text
.rfind("//")
.map(|pos| !before_text[pos..].contains('\n'))
.unwrap_or(false)
};
if is_boundary(before) && is_boundary(after) && !is_inside_line_comment {
result.push_str(new);
i += old_bytes.len();
continue;
}
}
result.push(bytes[i] as char);
i += 1;
}
result
}
pub fn process_file<P: AsRef<Path>>(&self, path: P) -> Result<usize, String> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.map_err(|e| format!("Cannot read '{}': {}", path.display(), e))?;
let mut count = 0;
for op in &self.operations {
if self.contains_symbol(&content, &op.old_name) {
count += 1;
}
}
Ok(count)
}
}
impl Default for X86ClangRename {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86FieldInfo {
pub name: String,
pub field_type: String,
pub size_bytes: usize,
pub alignment_bytes: usize,
pub offset_bytes: usize,
pub is_bitfield: bool,
pub bitfield_width: usize,
pub is_padding: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86TargetABI {
LP64,
ILP32,
LLP64,
}
impl fmt::Display for X86TargetABI {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::LP64 => write!(f, "LP64"),
Self::ILP32 => write!(f, "ILP32"),
Self::LLP64 => write!(f, "LLP64"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86LayoutAnalysis {
pub struct_name: String,
pub fields: Vec<X86FieldInfo>,
pub original_size: usize,
pub original_alignment: usize,
pub optimal_size: usize,
pub optimal_alignment: usize,
pub bytes_saved: usize,
pub padding_before: usize,
pub padding_after: usize,
pub target_abi: X86TargetABI,
}
#[derive(Debug, Clone)]
pub struct X86ClangReorderFields {
pub target_abi: X86TargetABI,
pub analyze_only: bool,
pub max_alignment: usize,
pub respect_alignas: bool,
pub analyses: Vec<X86LayoutAnalysis>,
pub verbose: bool,
}
impl X86ClangReorderFields {
pub fn new() -> Self {
Self {
target_abi: X86TargetABI::LP64,
analyze_only: true,
max_alignment: 16, respect_alignas: true,
analyses: Vec::new(),
verbose: false,
}
}
pub fn type_size_align(&self, type_name: &str) -> (usize, usize) {
let t = type_name.trim();
let t = t.trim_start_matches("const ");
let t = t.trim_start_matches("volatile ");
let t = t.trim_start_matches("unsigned ");
let t = t.trim_start_matches("signed ");
match t {
"char" | "int8_t" | "uint8_t" | "bool" => (1, 1),
"short" | "int16_t" | "uint16_t" | "wchar_t" => (2, 2),
"int" | "int32_t" | "uint32_t" | "float" | "long" => match self.target_abi {
X86TargetABI::LP64 => (4, 4),
X86TargetABI::ILP32 => (4, 4),
X86TargetABI::LLP64 => (4, 4),
},
"long long" | "int64_t" | "uint64_t" | "double" => (8, 8),
"long double" => match self.target_abi {
X86TargetABI::LP64 => (16, 16),
X86TargetABI::ILP32 => (12, 4),
X86TargetABI::LLP64 => (8, 8),
},
"__int128" | "__int128_t" | "__uint128_t" => (16, 16),
t if t.ends_with('*') || t.ends_with("&") => match self.target_abi {
X86TargetABI::LP64 => (8, 8),
X86TargetABI::ILP32 => (4, 4),
X86TargetABI::LLP64 => (8, 8),
},
_ => {
(8, 8)
}
}
}
pub fn parse_fields(&self, source: &str, struct_name: &str) -> Vec<X86FieldInfo> {
let mut fields = Vec::new();
let mut in_struct = false;
let mut brace_depth: i32 = 0;
let mut found_struct = false;
for line in source.lines() {
let trimmed = line.trim();
if trimmed.contains(&format!("struct {}", struct_name))
|| trimmed.contains(&format!("class {}", struct_name))
{
found_struct = true;
}
if found_struct && trimmed.contains('{') {
in_struct = true;
brace_depth += trimmed.matches('{').count() as i32;
brace_depth -= trimmed.matches('}').count() as i32;
continue;
}
if in_struct {
brace_depth += trimmed.matches('{').count() as i32;
brace_depth -= trimmed.matches('}').count() as i32;
if brace_depth <= 0 {
break;
}
if brace_depth == 1
&& !trimmed.is_empty()
&& !trimmed.starts_with("//")
&& !trimmed.starts_with("/*")
&& !trimmed.starts_with("public:")
&& !trimmed.starts_with("private:")
&& !trimmed.starts_with("protected:")
{
if let Some(field) = self.parse_field_line(trimmed) {
fields.push(field);
}
}
}
}
fields
}
fn parse_field_line(&self, line: &str) -> Option<X86FieldInfo> {
let trimmed = line.trim().trim_end_matches(';');
if trimmed.is_empty() || trimmed.starts_with("//") || trimmed.starts_with("/*") {
return None;
}
if trimmed.contains('(') && trimmed.contains(')') {
return None;
}
if trimmed == "public:" || trimmed == "private:" || trimmed == "protected:" {
return None;
}
let parts: Vec<&str> = trimmed.split_whitespace().collect();
if parts.len() < 2 {
return None;
}
let mut name_idx = parts.len() - 1;
let mut is_bitfield = false;
let mut bitfield_width = 0;
if let Some(colon_pos) = trimmed.rfind(':') {
is_bitfield = true;
let after_colon = trimmed[colon_pos + 1..].trim();
bitfield_width = after_colon.parse().unwrap_or(0);
let before_colon = &trimmed[..colon_pos].trim();
let before_parts: Vec<&str> = before_colon.split_whitespace().collect();
if let Some(name) = before_parts.last() {
if !Self::is_type_keyword(name) {
let type_str = before_parts[..before_parts.len() - 1].join(" ");
let (size, align) = self.type_size_align(&type_str);
return Some(X86FieldInfo {
name: name.to_string(),
field_type: type_str,
size_bytes: if is_bitfield {
(bitfield_width + 7) / 8
} else {
size
},
alignment_bytes: if is_bitfield { 1 } else { align },
offset_bytes: 0,
is_bitfield,
bitfield_width,
is_padding: false,
});
}
}
return None;
}
let last = parts[name_idx];
let trimmed_last = last.trim_end_matches(';').trim_end_matches(',');
if Self::is_type_keyword(trimmed_last) || trimmed_last.ends_with('*') {
return None;
}
let type_str = parts[..name_idx].join(" ");
let name = trimmed_last.to_string();
let (size, align) = self.type_size_align(&type_str);
Some(X86FieldInfo {
name,
field_type: type_str,
size_bytes: size,
alignment_bytes: align,
offset_bytes: 0,
is_bitfield: false,
bitfield_width: 0,
is_padding: false,
})
}
fn is_type_keyword(s: &str) -> bool {
matches!(
s,
"const"
| "volatile"
| "static"
| "mutable"
| "unsigned"
| "signed"
| "long"
| "short"
| "int"
| "char"
| "float"
| "double"
| "bool"
| "void"
| "auto"
| "struct"
| "class"
| "enum"
| "union"
)
}
pub fn compute_current_layout(&self, fields: &[X86FieldInfo]) -> (usize, usize, usize) {
let mut offset = 0;
let mut max_align = 1;
let mut padded_fields = Vec::new();
for field in fields {
let align = field.alignment_bytes.min(self.max_alignment);
max_align = max_align.max(align);
let misalignment = offset % align;
if misalignment != 0 {
let padding = align - misalignment;
offset += padding;
}
let mut f = field.clone();
f.offset_bytes = offset;
padded_fields.push(f);
offset += field.size_bytes;
}
let misalignment = offset % max_align;
let total_padding = if misalignment != 0 {
max_align - misalignment
} else {
0
};
let total_size = offset + total_padding;
(total_size, max_align, total_padding)
}
pub fn compute_optimal_layout(&self, fields: &[X86FieldInfo]) -> Vec<X86FieldInfo> {
let mut sorted: Vec<X86FieldInfo> = fields.to_vec();
sorted.sort_by(|a, b| {
b.alignment_bytes
.cmp(&a.alignment_bytes)
.then_with(|| b.size_bytes.cmp(&a.size_bytes))
});
sorted
}
pub fn analyze_struct(&mut self, source: &str, struct_name: &str) -> X86LayoutAnalysis {
let fields = self.parse_fields(source, struct_name);
let (original_size, original_align, padding) = self.compute_current_layout(&fields);
let optimal_fields = self.compute_optimal_layout(&fields);
let (optimal_size, optimal_align, _) = self.compute_current_layout(&optimal_fields);
let bytes_saved = original_size.saturating_sub(optimal_size);
X86LayoutAnalysis {
struct_name: struct_name.to_string(),
fields,
original_size,
original_alignment: original_align,
optimal_size,
optimal_alignment: optimal_align,
bytes_saved,
padding_before: padding,
padding_after: original_size - optimal_size,
target_abi: self.target_abi,
}
}
pub fn generate_reorder_patch(&self, analysis: &X86LayoutAnalysis) -> String {
let optimal = self.compute_optimal_layout(&analysis.fields);
let mut patch = String::new();
patch.push_str(&format!(
"// Reordered fields for '{}' ({} -> {} bytes, saved {} bytes)\n",
analysis.struct_name,
analysis.original_size,
analysis.optimal_size,
analysis.bytes_saved
));
for field in &optimal {
patch.push_str(&format!(
" {} {}; // {} bytes, align {}\n",
field.field_type, field.name, field.size_bytes, field.alignment_bytes
));
}
patch
}
pub fn size_comparison_report(&self, analysis: &X86LayoutAnalysis) -> String {
let mut report = String::new();
report.push_str(&format!("╔══════════════════════════════════════════╗\n"));
report.push_str(&format!(
"║ Layout Analysis: {:20} ║\n",
analysis.struct_name
));
report.push_str(&format!("╠══════════════════════════════════════════╣\n"));
report.push_str(&format!("║ Target ABI: {:24} ║\n", analysis.target_abi));
report.push_str(&format!(
"║ Original Size: {:>8} bytes ║\n",
analysis.original_size
));
report.push_str(&format!(
"║ Optimal Size: {:>8} bytes ║\n",
analysis.optimal_size
));
report.push_str(&format!(
"║ Bytes Saved: {:>8} ║\n",
analysis.bytes_saved
));
report.push_str(&format!(
"║ Padding: {:>8} bytes ║\n",
analysis.padding_before
));
report.push_str("╚══════════════════════════════════════════╝\n");
report.push_str("\nField Layout (Original):\n");
report.push_str("------------------------\n");
for field in &analysis.fields {
report.push_str(&format!(
" offset {:4} | size {:4} | align {:4} | {} {}\n",
field.offset_bytes,
field.size_bytes,
field.alignment_bytes,
field.field_type,
field.name
));
}
report.push_str("\nField Layout (Optimal):\n");
report.push_str("-----------------------\n");
let optimal = self.compute_optimal_layout(&analysis.fields);
let mut off = 0;
for field in &optimal {
let align = field.alignment_bytes.min(self.max_alignment);
let misalign = off % align;
if misalign != 0 {
off += align - misalign;
}
report.push_str(&format!(
" offset {:4} | size {:4} | align {:4} | {} {}\n",
off, field.size_bytes, field.alignment_bytes, field.field_type, field.name
));
off += field.size_bytes;
}
report
}
pub fn process_file<P: AsRef<Path>>(&mut self, path: P) -> Result<usize, String> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.map_err(|e| format!("Cannot read '{}': {}", path.display(), e))?;
let mut reordered = 0;
let struct_names = self.find_struct_names(&content);
for name in &struct_names {
let analysis = self.analyze_struct(&content, name);
if analysis.bytes_saved > 0 {
self.analyses.push(analysis);
reordered += 1;
}
}
Ok(reordered)
}
fn find_struct_names(&self, source: &str) -> Vec<String> {
let mut names = Vec::new();
for line in source.lines() {
let trimmed = line.trim();
if trimmed.starts_with("struct ") {
let after_struct = trimmed[7..].trim();
if let Some(name) = after_struct
.split(|c: char| c == '{' || c == ':' || c == ';')
.next()
{
let name = name.trim();
if !name.is_empty() && !Self::is_type_keyword(name) {
names.push(name.to_string());
}
}
}
if trimmed.starts_with("class ") && !trimmed.contains('(') {
let after_class = trimmed[6..].trim();
if let Some(name) = after_class
.split(|c: char| c == '{' || c == ':' || c == ';')
.next()
{
let name = name.trim();
if !name.is_empty() && !Self::is_type_keyword(name) {
names.push(name.to_string());
}
}
}
}
names
}
}
impl Default for X86ClangReorderFields {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86DependencyInfo {
pub file: PathBuf,
pub dependencies: Vec<PathBuf>,
pub module_dependencies: Vec<X86ModuleDep>,
pub command_line: Vec<String>,
pub hash: String,
}
#[derive(Debug, Clone)]
pub struct X86ModuleDep {
pub module_name: String,
pub module_file: PathBuf,
pub context_hash: String,
pub is_interface: bool,
pub imported_modules: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct X86CompileCommandEntry {
pub directory: String,
pub file: String,
pub command: String,
pub arguments: Vec<String>,
pub output: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86ScanDeps {
pub include_paths: Vec<PathBuf>,
pub system_include_paths: Vec<PathBuf>,
pub defines: Vec<(String, Option<String>)>,
pub scan_modules: bool,
pub generate_d_files: bool,
pub generate_compilation_db: bool,
pub target_triple: String,
pub seen_files: HashSet<PathBuf>,
pub dependencies: Vec<X86DependencyInfo>,
pub compilation_entries: Vec<X86CompileCommandEntry>,
pub verbose: bool,
}
impl X86ScanDeps {
pub fn new() -> Self {
Self {
include_paths: vec![PathBuf::from(".")],
system_include_paths: vec![
PathBuf::from("/usr/include"),
PathBuf::from("/usr/local/include"),
],
defines: Vec::new(),
scan_modules: true,
generate_d_files: false,
generate_compilation_db: false,
target_triple: "x86_64-unknown-linux-gnu".to_string(),
seen_files: HashSet::new(),
dependencies: Vec::new(),
compilation_entries: Vec::new(),
verbose: false,
}
}
pub fn scan_file<P: AsRef<Path>>(&mut self, path: P) -> Result<Vec<PathBuf>, String> {
let path = path.as_ref().to_path_buf();
self.seen_files.clear();
let deps = self.scan_file_recursive(&path, 0)?;
Ok(deps)
}
pub fn add_include_path<P: AsRef<Path>>(&mut self, path: P) {
self.include_paths.push(path.as_ref().to_path_buf());
}
pub fn add_define(&mut self, name: &str, value: Option<&str>) {
self.defines
.push((name.to_string(), value.map(|v| v.to_string())));
}
fn scan_file_recursive(&mut self, path: &Path, depth: usize) -> Result<Vec<PathBuf>, String> {
if depth > 100 {
return Ok(Vec::new()); }
if self.seen_files.contains(path) {
return Ok(Vec::new());
}
self.seen_files.insert(path.to_path_buf());
let content = fs::read_to_string(path)
.map_err(|e| format!("Cannot read '{}': {}", path.display(), e))?;
let mut deps = Vec::new();
for line in content.lines() {
let trimmed = line.trim();
if !trimmed.starts_with("#include") {
continue;
}
let include_path =
if let (Some(start), Some(end)) = (trimmed.find('"'), trimmed.rfind('"')) {
if start < end {
Some(trimmed[start + 1..end].to_string())
} else {
None
}
} else if let (Some(start), Some(end)) = (trimmed.find('<'), trimmed.find('>')) {
Some(trimmed[start + 1..end].to_string())
} else {
None
};
if let Some(inc_path) = include_path {
if let Some(resolved) = self.resolve_include(path, &inc_path) {
if !deps.contains(&resolved) {
deps.push(resolved.clone());
match self.scan_file_recursive(&resolved, depth + 1) {
Ok(sub_deps) => {
for sub in sub_deps {
if !deps.contains(&sub) {
deps.push(sub);
}
}
}
Err(_) => {} }
}
}
}
}
if self.scan_modules {
let module_deps = self.scan_module_imports(&content);
for md in &module_deps {
if let Some(resolved) =
self.resolve_include(path, &md.module_file.display().to_string())
{
if !deps.contains(&resolved) {
deps.push(resolved);
}
}
}
}
deps.sort();
deps.dedup();
Ok(deps)
}
fn resolve_include(&self, current_file: &Path, include: &str) -> Option<PathBuf> {
if let Some(parent) = current_file.parent() {
let relative = parent.join(include);
if relative.exists() {
return Some(relative);
}
}
for inc_dir in &self.include_paths {
let candidate = inc_dir.join(include);
if candidate.exists() {
return Some(candidate);
}
}
for sys_dir in &self.system_include_paths {
let candidate = sys_dir.join(include);
if candidate.exists() {
return Some(candidate);
}
}
None
}
fn scan_module_imports(&self, source: &str) -> Vec<X86ModuleDep> {
let mut modules = Vec::new();
for line in source.lines() {
let trimmed = line.trim();
if trimmed.starts_with("import ") {
let after_import = trimmed[7..].trim();
let module_name = after_import.trim_end_matches(';').trim().to_string();
if !module_name.is_empty() {
modules.push(X86ModuleDep {
module_name: module_name.clone(),
module_file: PathBuf::from(format!("{}.cppm", module_name)),
context_hash: self.compute_hash(&module_name),
is_interface: trimmed.contains("export"),
imported_modules: Vec::new(),
});
}
}
if trimmed.starts_with("export import ") {
let after_import = trimmed[14..].trim();
let module_name = after_import.trim_end_matches(';').trim().to_string();
if !module_name.is_empty() {
modules.push(X86ModuleDep {
module_name: module_name.clone(),
module_file: PathBuf::from(format!("{}.cppm", module_name)),
context_hash: self.compute_hash(&module_name),
is_interface: true,
imported_modules: Vec::new(),
});
}
}
}
modules
}
fn compute_hash(&self, s: &str) -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
s.hash(&mut hasher);
format!("{:016x}", hasher.finish())
}
pub fn generate_d_file(&self, info: &X86DependencyInfo) -> String {
let mut d = String::new();
d.push_str(&format!("{}:", info.file.display()));
for dep in &info.dependencies {
d.push_str(&format!(" \\\n {}", dep.display()));
}
d.push_str("\n\n");
for dep in &info.dependencies {
d.push_str(&format!("{}:\n", dep.display()));
}
d
}
pub fn write_d_file<P: AsRef<Path>>(
&self,
output_path: P,
info: &X86DependencyInfo,
) -> Result<(), String> {
let content = self.generate_d_file(info);
fs::write(output_path.as_ref(), content).map_err(|e| format!("Cannot write .d file: {}", e))
}
pub fn generate_compilation_entry(
&self,
file: &Path,
dir: &Path,
args: &[String],
) -> X86CompileCommandEntry {
let output = file.with_extension("o");
X86CompileCommandEntry {
directory: dir.display().to_string(),
file: file.display().to_string(),
command: format!("clang -c {} -o {}", file.display(), output.display()),
arguments: args.to_vec(),
output: Some(output.display().to_string()),
}
}
pub fn generate_compile_commands_json(&self) -> String {
let mut json = String::new();
json.push_str("[\n");
for (i, entry) in self.compilation_entries.iter().enumerate() {
json.push_str(" {\n");
json.push_str(&format!(" \"directory\": \"{}\",\n", entry.directory));
json.push_str(&format!(" \"file\": \"{}\",\n", entry.file));
json.push_str(&format!(
" \"arguments\": [{}],\n",
entry
.arguments
.iter()
.map(|a| format!("\"{}\"", a))
.collect::<Vec<_>>()
.join(", ")
));
if let Some(ref out) = entry.output {
json.push_str(&format!(" \"output\": \"{}\"\n", out));
}
json.push_str(" }");
if i + 1 < self.compilation_entries.len() {
json.push(',');
}
json.push('\n');
}
json.push_str("]\n");
json
}
pub fn scan_directory<P: AsRef<Path>>(
&mut self,
dir: P,
extensions: &[&str],
) -> Result<Vec<X86DependencyInfo>, String> {
let dir = dir.as_ref();
let mut results = Vec::new();
let entries = fs::read_dir(dir)
.map_err(|e| format!("Cannot read directory '{}': {}", dir.display(), e))?;
for entry in entries {
let entry = entry.map_err(|e| format!("Directory entry error: {}", e))?;
let path = entry.path();
if path.is_file() {
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
if extensions.contains(&ext) {
match self.scan_file(&path) {
Ok(deps) => {
let info = X86DependencyInfo {
file: path.clone(),
dependencies: deps,
module_dependencies: Vec::new(),
command_line: Vec::new(),
hash: self.compute_hash(&path.display().to_string()),
};
results.push(info);
}
Err(e) => {
if self.verbose {
eprintln!("Scan error for {}: {}", path.display(), e);
}
}
}
}
}
} else if path.is_dir() {
match self.scan_directory(&path, extensions) {
Ok(sub_results) => results.extend(sub_results),
Err(_) => {}
}
}
}
self.dependencies.extend(results.clone());
Ok(results)
}
}
impl Default for X86ScanDeps {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86DiagFlag {
pub name: String,
pub flag: String,
pub category: X86DiagCategory,
pub description: String,
pub default_severity: X86DiagSeverityLevel,
pub enabled_by_default: bool,
pub groups: Vec<String>,
pub since_version: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86DiagCategory {
Warning,
Error,
Note,
Remark,
Ignored,
}
impl fmt::Display for X86DiagCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Warning => write!(f, "warning"),
Self::Error => write!(f, "error"),
Self::Note => write!(f, "note"),
Self::Remark => write!(f, "remark"),
Self::Ignored => write!(f, "ignored"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum X86DiagSeverityLevel {
Ignored = 0,
Note = 1,
Remark = 2,
Warning = 3,
Error = 4,
Fatal = 5,
}
impl fmt::Display for X86DiagSeverityLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Ignored => write!(f, "ignored"),
Self::Note => write!(f, "note"),
Self::Remark => write!(f, "remark"),
Self::Warning => write!(f, "warning"),
Self::Error => write!(f, "error"),
Self::Fatal => write!(f, "fatal"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86DiagGroup {
pub name: String,
pub description: String,
pub parent_group: Option<String>,
pub flags: Vec<String>,
pub enabled_by_default: bool,
}
#[derive(Debug, Clone)]
pub struct X86DiagTool {
pub flags: Vec<X86DiagFlag>,
pub groups: Vec<X86DiagGroup>,
pub enabled_warnings: Vec<String>,
pub disabled_warnings: Vec<String>,
pub verbose: bool,
}
impl X86DiagTool {
pub fn new() -> Self {
let mut tool = Self {
flags: Vec::new(),
groups: Vec::new(),
enabled_warnings: Vec::new(),
disabled_warnings: Vec::new(),
verbose: false,
};
tool.register_default_diagnostics();
tool
}
fn register_default_diagnostics(&mut self) {
let groups = [
("-Wunused", "Warn about unused entities", None, true),
(
"-Wunused-variable",
"Warn about unused variables",
Some("-Wunused"),
true,
),
(
"-Wunused-function",
"Warn about unused functions",
Some("-Wunused"),
true,
),
(
"-Wunused-parameter",
"Warn about unused parameters",
Some("-Wunused"),
false,
),
(
"-Wunused-value",
"Warn about unused expression results",
Some("-Wunused"),
true,
),
(
"-Wunused-result",
"Warn about unused function results",
Some("-Wunused"),
false,
),
(
"-Wconversion",
"Warn about implicit conversions",
None,
false,
),
(
"-Wsign-conversion",
"Warn about sign conversions",
Some("-Wconversion"),
false,
),
(
"-Wfloat-conversion",
"Warn about float conversions",
Some("-Wconversion"),
false,
),
(
"-Wimplicit-int-conversion",
"Warn about implicit int conversions",
Some("-Wconversion"),
false,
),
("-Wall", "Enable most common warnings", None, false),
("-Wextra", "Enable extra warnings", None, false),
("-Wpedantic", "Enable pedantic warnings", None, false),
("-Wshadow", "Warn about variable shadowing", None, false),
(
"-Wformat",
"Warn about printf/scanf format issues",
None,
true,
),
(
"-Wimplicit-fallthrough",
"Warn about implicit fallthrough in switch",
None,
true,
),
(
"-Wnull-dereference",
"Warn about null pointer dereference",
None,
true,
),
(
"-Wreturn-type",
"Warn about return type mismatches",
None,
true,
),
(
"-Wparentheses",
"Warn about ambiguous parentheses",
None,
true,
),
("-Wswitch", "Warn about switch statement issues", None, true),
(
"-Wtautological-compare",
"Warn about tautological comparisons",
None,
true,
),
(
"-Wenum-compare",
"Warn about enum comparison mismatches",
None,
true,
),
(
"-Wdelete-non-virtual-dtor",
"Warn about delete on non-virtual destructors",
None,
true,
),
(
"-Woverloaded-virtual",
"Warn about overloaded virtual functions",
None,
true,
),
(
"-Wreorder",
"Warn about member initializer order",
None,
true,
),
(
"-Wnon-virtual-dtor",
"Warn about non-virtual destructors",
None,
true,
),
("-Wold-style-cast", "Warn about C-style casts", None, false),
("-Wcast-align", "Warn about cast alignment", None, false),
(
"-Wcast-qual",
"Warn about cast discarding qualifiers",
None,
false,
),
(
"-Wwrite-strings",
"Warn about writing to string literals",
None,
true,
),
(
"-Wmissing-field-initializers",
"Warn about missing field initializers",
None,
false,
),
(
"-Wmissing-braces",
"Warn about missing braces in initializer",
None,
true,
),
(
"-Wsign-compare",
"Warn about signed/unsigned comparisons",
None,
true,
),
(
"-Wpointer-arith",
"Warn about pointer arithmetic on void*",
None,
true,
),
(
"-Wstrict-prototypes",
"Warn about non-prototype function declarations",
None,
false,
),
(
"-Wmissing-prototypes",
"Warn about missing function prototypes",
None,
false,
),
("-Winline", "Warn about failed inline", None, false),
("-Wnoexcept", "Warn about noexcept issues", None, false),
("-Wdeprecated", "Warn about deprecated features", None, true),
(
"-Wdeprecated-declarations",
"Warn about deprecated declarations",
Some("-Wdeprecated"),
true,
),
(
"-Wdocumentation",
"Warn about documentation issues",
None,
false,
),
(
"-Wunknown-pragmas",
"Warn about unknown pragmas",
None,
true,
),
(
"-Wpragma-once-outside-header",
"Warn about #pragma once outside headers",
None,
true,
),
(
"-Wexpansion-to-defined",
"Warn about expansion to defined()",
None,
true,
),
(
"-Wvariadic-macros",
"Warn about variadic macro usage",
None,
false,
),
(
"-Wheader-hygiene",
"Warn about using namespace in headers",
None,
false,
),
(
"-Wnewline-eof",
"Warn about missing newline at EOF",
None,
false,
),
("-Wtrigraphs", "Warn about trigraph usage", None, false),
(
"-Wc++11-extensions",
"Warn about C++11 extensions",
None,
false,
),
(
"-Wc++14-extensions",
"Warn about C++14 extensions",
None,
false,
),
(
"-Wc++17-extensions",
"Warn about C++17 extensions",
None,
false,
),
(
"-Wc++20-extensions",
"Warn about C++20 extensions",
None,
false,
),
(
"-Wzero-length-array",
"Warn about zero-length arrays",
None,
true,
),
("-Wvla", "Warn about variable-length arrays", None, false),
(
"-Wflexible-array-extensions",
"Warn about flexible array extensions",
None,
false,
),
("-Wgnu", "Warn about GNU extensions", None, false),
(
"-Wmicrosoft",
"Warn about Microsoft extensions",
None,
false,
),
(
"-Wclang-cl-pch",
"Warn about Clang-CL PCH issues",
None,
false,
),
];
for (flag, desc, parent, enabled) in &groups {
self.groups.push(X86DiagGroup {
name: flag.to_string(),
description: desc.to_string(),
parent_group: parent.map(|s| s.to_string()),
flags: vec![flag.to_string()],
enabled_by_default: *enabled,
});
let category = if flag.starts_with("-W") {
X86DiagCategory::Warning
} else if flag.starts_with("-R") {
X86DiagCategory::Remark
} else {
X86DiagCategory::Warning
};
self.flags.push(X86DiagFlag {
name: flag.replacen('-', "", 1).replace('-', "_"),
flag: flag.to_string(),
category,
description: desc.to_string(),
default_severity: if *enabled {
X86DiagSeverityLevel::Warning
} else {
X86DiagSeverityLevel::Ignored
},
enabled_by_default: *enabled,
groups: parent.map(|p| vec![p.to_string()]).unwrap_or_default(),
since_version: None,
});
}
self.refresh_enabled_state();
}
pub fn refresh_enabled_state(&mut self) {
self.enabled_warnings.clear();
self.disabled_warnings.clear();
for flag in &self.flags {
if flag.enabled_by_default {
self.enabled_warnings.push(flag.flag.clone());
} else {
self.disabled_warnings.push(flag.flag.clone());
}
}
}
pub fn enable_warning(&mut self, flag: &str) {
let flag = if flag.starts_with('-') {
flag.to_string()
} else {
format!("-W{}", flag)
};
if let Some(f) = self.flags.iter_mut().find(|f| f.flag == flag) {
f.enabled_by_default = true;
f.default_severity = X86DiagSeverityLevel::Warning;
}
self.refresh_enabled_state();
}
pub fn disable_warning(&mut self, flag: &str) {
let flag = if flag.starts_with('-') {
flag.to_string()
} else {
format!("-W{}", flag)
};
if let Some(f) = self.flags.iter_mut().find(|f| f.flag == flag) {
f.enabled_by_default = false;
f.default_severity = X86DiagSeverityLevel::Ignored;
}
self.refresh_enabled_state();
}
pub fn list_enabled_warnings(&self) -> Vec<&X86DiagFlag> {
self.flags.iter().filter(|f| f.enabled_by_default).collect()
}
pub fn list_disabled_warnings(&self) -> Vec<&X86DiagFlag> {
self.flags
.iter()
.filter(|f| !f.enabled_by_default)
.collect()
}
pub fn get_flag_doc(&self, flag: &str) -> Option<&X86DiagFlag> {
self.flags.iter().find(|f| f.flag == flag || f.name == flag)
}
pub fn search_flags(&self, keyword: &str) -> Vec<&X86DiagFlag> {
let kw = keyword.to_lowercase();
self.flags
.iter()
.filter(|f| {
f.name.to_lowercase().contains(&kw)
|| f.description.to_lowercase().contains(&kw)
|| f.flag.to_lowercase().contains(&kw)
})
.collect()
}
pub fn count_by_group(&self) -> BTreeMap<String, usize> {
let mut counts: BTreeMap<String, usize> = BTreeMap::new();
for group in &self.groups {
let count = self
.flags
.iter()
.filter(|f| f.groups.contains(&group.name))
.count();
if count > 0 {
counts.insert(group.name.clone(), count);
}
}
counts
}
pub fn browse_groups(&self) -> String {
let mut browser = String::new();
browser.push_str("╔══════════════════════════════════════════════════════════════╗\n");
browser.push_str("║ Clang Diagnostic Group Browser ║\n");
browser.push_str("╠══════════════════════════════════════════════════════════════╣\n");
for group in &self.groups {
let status = if group.enabled_by_default {
"ON "
} else {
"OFF"
};
let flags_in_group = self
.flags
.iter()
.filter(|f| f.groups.contains(&group.name))
.count();
browser.push_str(&format!("║ [{:3}] {:45} ║\n", status, group.name));
if let Some(ref parent) = group.parent_group {
browser.push_str(&format!("║ └─ parent: {:38} ║\n", parent));
}
browser.push_str(&format!(
"║ {} ({} flags) ║\n",
group.description, flags_in_group
));
}
browser.push_str("╚══════════════════════════════════════════════════════════════╝\n");
browser
}
pub fn warning_summary(&self) -> String {
let mut summary = String::new();
summary.push_str("Warning Summary by Category:\n");
summary.push_str("============================\n");
let categories = [
X86DiagCategory::Warning,
X86DiagCategory::Error,
X86DiagCategory::Note,
X86DiagCategory::Remark,
X86DiagCategory::Ignored,
];
for cat in &categories {
let count = self.flags.iter().filter(|f| f.category == *cat).count();
let enabled = self
.flags
.iter()
.filter(|f| f.category == *cat && f.enabled_by_default)
.count();
summary.push_str(&format!(
" {:10}: {:4} total, {:4} enabled\n",
cat.to_string(),
count,
enabled
));
}
summary
}
pub fn analyze_file<P: AsRef<Path>>(&self, path: P) -> Result<usize, String> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.map_err(|e| format!("Cannot read '{}': {}", path.display(), e))?;
let mut diagnostic_count = 0;
for line in content.lines() {
let trimmed = line.trim();
if trimmed.contains("unused") {
diagnostic_count += 1;
}
if trimmed.contains("int ")
&& trimmed.contains("= ")
&& (trimmed.contains("double") || trimmed.contains("float"))
{
diagnostic_count += 1;
}
if trimmed.contains("unsigned ") && trimmed.contains(" < 0") {
diagnostic_count += 1;
}
if trimmed.contains("case ") && !line.contains("break") && !line.contains("fallthrough")
{
diagnostic_count += 1;
}
}
Ok(diagnostic_count)
}
}
impl Default for X86DiagTool {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86DocDatabase {
pub entries: Vec<X86DocComment>,
pub index_by_name: BTreeMap<String, usize>,
pub index_by_kind: BTreeMap<X86DocSymbolKind, Vec<usize>>,
pub cross_references: BTreeMap<String, Vec<String>>,
pub modified: bool,
pub db_path: Option<PathBuf>,
}
impl X86DocDatabase {
pub fn new() -> Self {
Self {
entries: Vec::new(),
index_by_name: BTreeMap::new(),
index_by_kind: BTreeMap::new(),
cross_references: BTreeMap::new(),
modified: false,
db_path: None,
}
}
pub fn add_entry(&mut self, entry: X86DocComment) {
let idx = self.entries.len();
self.index_by_name.insert(entry.symbol_name.clone(), idx);
self.index_by_kind
.entry(entry.symbol_kind)
.or_default()
.push(idx);
for see in &entry.see_also {
self.cross_references
.entry(see.clone())
.or_default()
.push(entry.symbol_name.clone());
}
for rel in &entry.related {
self.cross_references
.entry(rel.clone())
.or_default()
.push(entry.symbol_name.clone());
}
self.entries.push(entry);
self.modified = true;
}
pub fn lookup(&self, name: &str) -> Option<&X86DocComment> {
self.index_by_name.get(name).map(|&idx| &self.entries[idx])
}
pub fn by_kind(&self, kind: X86DocSymbolKind) -> Vec<&X86DocComment> {
self.index_by_kind
.get(&kind)
.map(|indices| indices.iter().map(|&i| &self.entries[i]).collect())
.unwrap_or_default()
}
pub fn referenced_by(&self, name: &str) -> Vec<&X86DocComment> {
self.cross_references
.get(name)
.map(|refs| refs.iter().filter_map(|n| self.lookup(n)).collect())
.unwrap_or_default()
}
pub fn search(&self, keyword: &str) -> Vec<&X86DocComment> {
let kw = keyword.to_lowercase();
self.entries
.iter()
.filter(|e| {
e.symbol_name.to_lowercase().contains(&kw)
|| e.brief.to_lowercase().contains(&kw)
|| e.full_text.to_lowercase().contains(&kw)
})
.collect()
}
pub fn export_yaml(&self) -> String {
let mut doc = X86ClangDoc::new();
doc.comments = self.entries.clone();
doc.generate_yaml()
}
pub fn export_json(&self) -> String {
let mut doc = X86ClangDoc::new();
doc.comments = self.entries.clone();
doc.generate_json()
}
}
impl Default for X86DocDatabase {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86IncludeMap {
pub mappings: Vec<X86IncludeMapping>,
pub transitive: BTreeMap<String, Vec<String>>,
pub conditional: BTreeMap<String, Vec<(String, String)>>,
}
impl X86IncludeMap {
pub fn new() -> Self {
let mut map = Self {
mappings: Vec::new(),
transitive: BTreeMap::new(),
conditional: BTreeMap::new(),
};
map.load_defaults();
map
}
fn load_defaults(&mut self) {
let posix = [
("open", "fcntl.h"),
("close", "unistd.h"),
("read", "unistd.h"),
("write", "unistd.h"),
("lseek", "unistd.h"),
("stat", "sys/stat.h"),
("fstat", "sys/stat.h"),
("lstat", "sys/stat.h"),
("chmod", "sys/stat.h"),
("fork", "unistd.h"),
("execve", "unistd.h"),
("waitpid", "sys/wait.h"),
("kill", "signal.h"),
("signal", "signal.h"),
("sigaction", "signal.h"),
("mmap", "sys/mman.h"),
("munmap", "sys/mman.h"),
("mprotect", "sys/mman.h"),
("socket", "sys/socket.h"),
("connect", "sys/socket.h"),
("bind", "sys/socket.h"),
("listen", "sys/socket.h"),
("accept", "sys/socket.h"),
("send", "sys/socket.h"),
("recv", "sys/socket.h"),
("getaddrinfo", "netdb.h"),
("inet_ntoa", "arpa/inet.h"),
("inet_ntop", "arpa/inet.h"),
];
for (sym, hdr) in &posix {
self.add_mapping(sym, hdr, true, X86IncludeLanguage::C);
}
let cpp_extra = [
("std::cerr", "iostream"),
("std::clog", "iostream"),
("std::wcout", "iostream"),
("std::wcin", "iostream"),
("std::getline", "string"),
("std::stoi", "string"),
("std::stod", "string"),
("std::to_string", "string"),
("std::swap", "utility"),
("std::exchange", "utility"),
("std::as_const", "utility"),
("std::begin", "iterator"),
("std::end", "iterator"),
("std::next", "iterator"),
("std::prev", "iterator"),
("std::advance", "iterator"),
("std::distance", "iterator"),
("std::back_inserter", "iterator"),
("std::front_inserter", "iterator"),
("std::inserter", "iterator"),
("std::make_pair", "utility"),
("std::tie", "tuple"),
("std::make_tuple", "tuple"),
("std::get", "tuple"),
];
for (sym, hdr) in &cpp_extra {
self.add_mapping(sym, hdr, true, X86IncludeLanguage::Cpp);
}
self.transitive
.insert("std::string".into(), vec!["string".into(), "iosfwd".into()]);
self.transitive.insert(
"std::vector".into(),
vec!["vector".into(), "algorithm".into()],
);
}
fn add_mapping(
&mut self,
symbol: &str,
header: &str,
is_system: bool,
language: X86IncludeLanguage,
) {
self.mappings.push(X86IncludeMapping {
symbol: symbol.to_string(),
header: header.to_string(),
is_system,
language,
});
}
pub fn lookup(&self, symbol: &str) -> Option<&str> {
self.mappings
.iter()
.find(|m| m.symbol == symbol)
.map(|m| m.header.as_str())
}
pub fn transitive_headers(&self, symbol: &str) -> Vec<&str> {
self.transitive
.get(symbol)
.map(|v| v.iter().map(|s| s.as_str()).collect())
.unwrap_or_default()
}
}
impl Default for X86IncludeMap {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86RefactoringBatch {
pub rename_ops: Vec<X86RenameOperation>,
pub move_ops: Vec<X86ClangMoveOperation>,
pub namespace_changes: Vec<(X86NamespacePath, X86NamespacePath)>,
pub field_reorders: Vec<String>,
}
impl X86RefactoringBatch {
pub fn new() -> Self {
Self {
rename_ops: Vec::new(),
move_ops: Vec::new(),
namespace_changes: Vec::new(),
field_reorders: Vec::new(),
}
}
pub fn add_rename(&mut self, op: X86RenameOperation) {
self.rename_ops.push(op);
}
pub fn add_move(&mut self, op: X86ClangMoveOperation) {
self.move_ops.push(op);
}
pub fn is_empty(&self) -> bool {
self.rename_ops.is_empty()
&& self.move_ops.is_empty()
&& self.namespace_changes.is_empty()
&& self.field_reorders.is_empty()
}
pub fn total_operations(&self) -> usize {
self.rename_ops.len()
+ self.move_ops.len()
+ self.namespace_changes.len()
+ self.field_reorders.len()
}
}
impl Default for X86RefactoringBatch {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86DiagnosticSeverityMap {
pub mappings: BTreeMap<String, X86DiagSeverityLevel>,
pub warn_as_error: bool,
pub error_as_fatal: bool,
}
impl X86DiagnosticSeverityMap {
pub fn new() -> Self {
Self {
mappings: BTreeMap::new(),
warn_as_error: false,
error_as_fatal: false,
}
}
pub fn promote_warning_group(&mut self, group: &str) {
self.mappings
.insert(group.to_string(), X86DiagSeverityLevel::Error);
}
pub fn suppress_warning_group(&mut self, group: &str) {
self.mappings
.insert(group.to_string(), X86DiagSeverityLevel::Ignored);
}
pub fn effective_severity(&self, flag: &X86DiagFlag) -> X86DiagSeverityLevel {
for group in &flag.groups {
if let Some(severity) = self.mappings.get(group) {
return *severity;
}
}
if self.warn_as_error && flag.default_severity == X86DiagSeverityLevel::Warning {
return X86DiagSeverityLevel::Error;
}
if self.error_as_fatal && flag.default_severity == X86DiagSeverityLevel::Error {
return X86DiagSeverityLevel::Fatal;
}
flag.default_severity
}
pub fn clear(&mut self) {
self.mappings.clear();
self.warn_as_error = false;
self.error_as_fatal = false;
}
}
impl Default for X86DiagnosticSeverityMap {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tools_full_new() {
let tools = X86ClangToolsFull::new();
assert_eq!(tools.working_dir, PathBuf::from("."));
assert!(tools.source_files.is_empty());
assert!(!tools.verbose);
}
#[test]
fn test_tools_full_with_working_dir() {
let tools = X86ClangToolsFull::with_working_dir("/tmp");
assert_eq!(tools.working_dir, PathBuf::from("/tmp"));
}
#[test]
fn test_tools_full_add_file() {
let mut tools = X86ClangToolsFull::new();
tools.add_file("test.c");
assert_eq!(tools.source_files.len(), 1);
}
#[test]
fn test_tools_full_set_verbose() {
let mut tools = X86ClangToolsFull::new();
tools.set_verbose(true);
assert!(tools.verbose);
assert!(tools.apply_replacements.verbose);
assert!(tools.change_namespace.verbose);
assert!(tools.doc.verbose);
assert!(tools.include_fixer.verbose);
assert!(tools.clang_move.verbose);
assert!(tools.rename.verbose);
assert!(tools.reorder_fields.verbose);
assert!(tools.scan_deps.verbose);
assert!(tools.diag_tool.verbose);
}
#[test]
fn test_tools_full_result_summary() {
let result = X86ToolsFullResult {
replacements_applied: 5,
namespace_changes: 2,
files_documented: 3,
includes_fixed: 10,
moves_applied: 1,
renames_applied: 4,
fields_reordered: 6,
deps_scanned: 15,
diagnostics_found: 8,
};
assert_eq!(result.total_operations(), 54);
let summary = result.summary();
assert!(summary.contains("54"));
}
#[test]
fn test_source_location_display() {
let loc = X86SourceLocation::new(10, 5);
assert_eq!(loc.to_string(), "10:5");
}
#[test]
fn test_source_range_contains() {
let range = X86SourceRange::new(5, 1, 10, 80);
assert!(range.contains(7, 10));
assert!(!range.contains(4, 1));
assert!(!range.contains(11, 1));
}
#[test]
fn test_source_range_overlaps() {
let a = X86SourceRange::new(1, 1, 5, 80);
let b = X86SourceRange::new(3, 1, 7, 80);
let c = X86SourceRange::new(6, 1, 10, 80);
assert!(a.overlaps(&b));
assert!(!a.overlaps(&c));
}
#[test]
fn test_replacement_apply() {
let repl = X86Replacement::new("f.c", 4, 5, "hello", "change word");
let result = repl.apply("int word = 5;").unwrap();
assert_eq!(result, "int hello = 5;");
}
#[test]
fn test_replacement_from_position() {
let source = "int main() {\n return 0;\n}\n";
let start = X86SourceLocation::new(1, 5);
let end = X86SourceLocation::new(1, 9);
let repl = X86Replacement::from_position("f.c", start, end, source, "foo", "rename");
let result = repl.apply(source).unwrap();
assert_eq!(result, "int foo() {\n return 0;\n}\n");
}
#[test]
fn test_replacement_conflicts() {
let a = X86Replacement::new("f.c", 10, 5, "A", "desc");
let b = X86Replacement::new("f.c", 12, 5, "B", "desc");
assert!(a.conflicts_with(&b));
let c = X86Replacement::new("f.c", 20, 5, "C", "desc");
assert!(!a.conflicts_with(&c));
let d = X86Replacement::new("other.c", 10, 5, "D", "desc");
assert!(!a.conflicts_with(&d));
}
#[test]
fn test_apply_replacements_new() {
let ar = X86ClangApplyReplacements::new();
assert!(!ar.config.dry_run);
assert!(ar.config.detect_conflicts);
assert!(ar.config.deduplicate);
}
#[test]
fn test_parse_yaml_replacements() {
let yaml = r#"---
MainSourceFile: 'test.c'
Replacements:
- FilePath: 'test.c'
Offset: 5
Length: 6
ReplacementText: 'fixed'
"#;
let mut ar = X86ClangApplyReplacements::new();
let count = ar
.parse_yaml_replacements(yaml, Path::new("f.yaml"))
.unwrap();
assert_eq!(count, 1);
assert_eq!(ar.total_replacements(), 1);
}
#[test]
fn test_parse_yaml_multiple_replacements() {
let yaml = r#"---
MainSourceFile: 'test.c'
Replacements:
- FilePath: 'test.c'
Offset: 0
Length: 3
ReplacementText: 'AAA'
- FilePath: 'test.c'
Offset: 10
Length: 3
ReplacementText: 'BBB'
- FilePath: 'other.c'
Offset: 5
Length: 2
ReplacementText: 'CC'
"#;
let mut ar = X86ClangApplyReplacements::new();
let count = ar
.parse_yaml_replacements(yaml, Path::new("f.yaml"))
.unwrap();
assert_eq!(count, 3);
}
#[test]
fn test_parse_json_replacements() {
let json = r#"[
{"FilePath": "test.c", "Offset": 0, "Length": 3, "ReplacementText": "fix", "Description": "test"}
]"#;
let mut ar = X86ClangApplyReplacements::new();
let count = ar
.parse_json_replacements(json, Path::new("f.json"))
.unwrap();
assert_eq!(count, 1);
}
#[test]
fn test_detect_conflicts() {
let mut ar = X86ClangApplyReplacements::new();
ar.add_replacement(X86Replacement::new("f.c", 10, 5, "A", ""));
ar.add_replacement(X86Replacement::new("f.c", 12, 5, "B", ""));
let conflicts = ar.detect_all_conflicts();
assert_eq!(conflicts, 1);
}
#[test]
fn test_resolve_conflicts_keep_first() {
let mut ar = X86ClangApplyReplacements::new();
ar.add_replacement(X86Replacement::new("f.c", 10, 5, "A", ""));
ar.add_replacement(X86Replacement::new("f.c", 12, 5, "B", ""));
ar.detect_all_conflicts();
let resolved = ar.resolve_conflicts(X86ConflictResolution::KeepFirst);
assert_eq!(resolved, 1);
}
#[test]
fn test_apply_replacements_dry_run() {
let mut ar = X86ClangApplyReplacements::new();
ar.config.dry_run = true;
let result = ar.process_file("/nonexistent/file.c");
assert!(result.is_err());
}
#[test]
fn test_namespace_path_from_str() {
let ns = X86NamespacePath::from_str("foo::bar::baz");
assert_eq!(ns.to_string(), "foo::bar::baz");
assert_eq!(ns.components.len(), 3);
}
#[test]
fn test_namespace_path_empty() {
let ns = X86NamespacePath::empty();
assert!(ns.is_empty());
assert_eq!(ns.to_string(), "");
}
#[test]
fn test_namespace_path_parent() {
let ns = X86NamespacePath::from_str("a::b::c");
let parent = ns.parent().unwrap();
assert_eq!(parent.to_string(), "a::b");
}
#[test]
fn test_change_namespace_new() {
let cn = X86ClangChangeNamespace::new();
assert!(cn.source_namespace.is_empty());
assert!(cn.target_namespace.is_empty());
assert!(cn.update_qualifiers);
assert!(cn.fix_using_declarations);
assert!(cn.handle_template_specializations);
}
#[test]
fn test_change_namespace_set_namespaces() {
let mut cn = X86ClangChangeNamespace::new();
cn.set_namespaces("old::ns", "new::ns");
assert_eq!(cn.source_namespace.to_string(), "old::ns");
assert_eq!(cn.target_namespace.to_string(), "new::ns");
}
#[test]
fn test_classify_declaration_line() {
assert_eq!(
X86ClangChangeNamespace::classify_declaration_line("class Foo {"),
Some(X86NamespaceDeclType::Class)
);
assert_eq!(
X86ClangChangeNamespace::classify_declaration_line("struct Bar {"),
Some(X86NamespaceDeclType::Struct)
);
assert_eq!(
X86ClangChangeNamespace::classify_declaration_line("enum Color {"),
Some(X86NamespaceDeclType::Enum)
);
assert_eq!(
X86ClangChangeNamespace::classify_declaration_line("template<typename T> class Vec {"),
Some(X86NamespaceDeclType::TemplateClass)
);
}
#[test]
fn test_extract_decl_name() {
assert_eq!(
X86ClangChangeNamespace::extract_decl_name("class MyClass {"),
Some("MyClass".into())
);
assert_eq!(
X86ClangChangeNamespace::extract_decl_name("void myFunc(int x)"),
Some("myFunc".into())
);
assert_eq!(
X86ClangChangeNamespace::extract_decl_name("int globalVar;"),
Some("globalVar".into())
);
assert_eq!(
X86ClangChangeNamespace::extract_decl_name("template<typename T> class Vec {"),
Some("Vec".into())
);
}
#[test]
fn test_replace_namespace_in_source() {
let source = "namespace old_ns {\nint x;\n}";
let from = X86NamespacePath::from_str("old_ns");
let to = X86NamespacePath::from_str("new_ns");
let result = X86ClangChangeNamespace::replace_namespace_in_source(source, &from, &to, "x");
assert!(result.contains("namespace new_ns"));
assert!(!result.contains("namespace old_ns"));
}
#[test]
fn test_doc_new() {
let doc = X86ClangDoc::new();
assert!(doc.comments.is_empty());
assert!(matches!(doc.output_format, X86DocOutputFormat::Markdown));
}
#[test]
fn test_doc_extract_triple_slash() {
let source = "/// Brief description\n///\n/// More details\nint foo() { return 0; }";
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source(source, Path::new("test.c"))
.unwrap();
assert!(!comments.is_empty());
assert_eq!(comments[0].symbol_name, "foo");
assert_eq!(comments[0].brief, "Brief description");
}
#[test]
fn test_doc_extract_javadoc() {
let source = "/**\n * Brief description\n * @param x the value\n * @return result\n */\nint bar(int x) { return x; }";
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source(source, Path::new("test.c"))
.unwrap();
assert!(!comments.is_empty());
assert_eq!(comments[0].symbol_name, "bar");
assert_eq!(comments[0].params.len(), 1);
assert_eq!(comments[0].params[0].name, "x");
assert_eq!(comments[0].return_desc.as_deref(), Some("result"));
}
#[test]
fn test_doc_extract_double_star_slash() {
let source = "/** Brief description */\nint baz() { return 0; }";
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source(source, Path::new("test.c"))
.unwrap();
assert!(!comments.is_empty());
}
#[test]
fn test_doc_generate_markdown() {
let mut doc = X86ClangDoc::new();
doc.comments.push(X86DocComment {
symbol_name: "test_func".into(),
symbol_kind: X86DocSymbolKind::Function,
brief: "A test function".into(),
full_text: "Full description".into(),
params: vec![X86DocParam {
name: "x".into(),
description: "input value".into(),
direction: X86DocParamDirection::In,
}],
return_desc: Some("the result".into()),
exceptions: Vec::new(),
see_also: vec!["other_func".into()],
related: Vec::new(),
notes: Vec::new(),
warnings: Vec::new(),
preconditions: Vec::new(),
postconditions: Vec::new(),
deprecated: None,
since: Some("1.0".into()),
authors: Vec::new(),
file_path: PathBuf::from("test.c"),
line: 42,
});
let md = doc.generate_markdown();
assert!(md.contains("test_func"));
assert!(md.contains("A test function"));
assert!(md.contains("x"));
assert!(md.contains("Returns"));
assert!(md.contains("other_func"));
}
#[test]
fn test_doc_generate_html() {
let mut doc = X86ClangDoc::new();
doc.comments.push(X86DocComment {
symbol_name: "myFunc".into(),
symbol_kind: X86DocSymbolKind::Function,
brief: "Does things".into(),
full_text: String::new(),
params: Vec::new(),
return_desc: None,
exceptions: Vec::new(),
see_also: Vec::new(),
related: Vec::new(),
notes: Vec::new(),
warnings: Vec::new(),
preconditions: Vec::new(),
postconditions: Vec::new(),
deprecated: None,
since: None,
authors: Vec::new(),
file_path: PathBuf::from("test.c"),
line: 1,
});
let html = doc.generate_html();
assert!(html.contains("<!DOCTYPE html>"));
assert!(html.contains("myFunc"));
assert!(html.contains("Does things"));
}
#[test]
fn test_doc_generate_man_page() {
let mut doc = X86ClangDoc::new();
doc.project_name = "TestLib".into();
doc.comments.push(X86DocComment {
symbol_name: "foo".into(),
symbol_kind: X86DocSymbolKind::Function,
brief: "Foo function".into(),
full_text: String::new(),
params: Vec::new(),
return_desc: None,
exceptions: Vec::new(),
see_also: Vec::new(),
related: Vec::new(),
notes: Vec::new(),
warnings: Vec::new(),
preconditions: Vec::new(),
postconditions: Vec::new(),
deprecated: None,
since: None,
authors: Vec::new(),
file_path: PathBuf::from("test.c"),
line: 1,
});
let man = doc.generate_man_page();
assert!(man.contains(".TH"));
assert!(man.contains("TESTLIB"));
assert!(man.contains("foo"));
}
#[test]
fn test_doc_generate_yaml() {
let mut doc = X86ClangDoc::new();
doc.project_name = "Test".into();
doc.comments.push(X86DocComment {
symbol_name: "test".into(),
symbol_kind: X86DocSymbolKind::Variable,
brief: "A variable".into(),
full_text: String::new(),
params: Vec::new(),
return_desc: None,
exceptions: Vec::new(),
see_also: Vec::new(),
related: Vec::new(),
notes: Vec::new(),
warnings: Vec::new(),
preconditions: Vec::new(),
postconditions: Vec::new(),
deprecated: None,
since: None,
authors: Vec::new(),
file_path: PathBuf::from("test.c"),
line: 5,
});
let yaml = doc.generate_yaml();
assert!(yaml.contains("project: Test"));
assert!(yaml.contains("name: test"));
assert!(yaml.contains("kind: variable"));
}
#[test]
fn test_doc_generate_json() {
let mut doc = X86ClangDoc::new();
doc.project_name = "Test".into();
doc.comments.push(X86DocComment {
symbol_name: "x".into(),
symbol_kind: X86DocSymbolKind::Variable,
brief: "desc".into(),
full_text: String::new(),
params: Vec::new(),
return_desc: None,
exceptions: Vec::new(),
see_also: Vec::new(),
related: Vec::new(),
notes: Vec::new(),
warnings: Vec::new(),
preconditions: Vec::new(),
postconditions: Vec::new(),
deprecated: None,
since: None,
authors: Vec::new(),
file_path: PathBuf::from("test.c"),
line: 1,
});
let json = doc.generate_json();
assert!(json.contains("\"project\": \"Test\""));
assert!(json.contains("\"name\": \"x\""));
}
#[test]
fn test_doc_extract_symbol_name() {
assert_eq!(
X86ClangDoc::extract_symbol_name(&" ").map(|s| s.to_string()),
None
);
}
#[test]
fn test_doc_classify_symbol() {
assert_eq!(
X86ClangDoc::classify_symbol_from_line("class Foo {"),
X86DocSymbolKind::Class
);
assert_eq!(
X86ClangDoc::classify_symbol_from_line("struct Bar {"),
X86DocSymbolKind::Struct
);
assert_eq!(
X86ClangDoc::classify_symbol_from_line("void func() {"),
X86DocSymbolKind::Function
);
assert_eq!(
X86ClangDoc::classify_symbol_from_line("#define PI 3.14"),
X86DocSymbolKind::Macro
);
}
#[test]
fn test_doc_build_cross_reference_index() {
let mut doc = X86ClangDoc::new();
doc.comments.push(X86DocComment {
symbol_name: "A".into(),
symbol_kind: X86DocSymbolKind::Function,
brief: String::new(),
full_text: String::new(),
params: Vec::new(),
return_desc: None,
exceptions: Vec::new(),
see_also: vec!["B".into(), "C".into()],
related: Vec::new(),
notes: Vec::new(),
warnings: Vec::new(),
preconditions: Vec::new(),
postconditions: Vec::new(),
deprecated: None,
since: None,
authors: Vec::new(),
file_path: PathBuf::from("test.c"),
line: 1,
});
let index = doc.build_cross_reference_index();
assert!(index.contains_key("B"));
assert!(index.contains_key("C"));
}
#[test]
fn test_include_fixer_new() {
let fixer = X86ClangIncludeFixer::new();
assert!(fixer.enabled);
assert!(!fixer.include_mappings.is_empty());
}
#[test]
fn test_find_missing_includes() {
let fixer = X86ClangIncludeFixer::new();
let source = "int main() { printf(\"hi\"); return 0; }";
let missing = fixer.find_missing_includes(source);
assert!(missing.contains(&"stdio.h".to_string()));
}
#[test]
fn test_find_unused_includes() {
let fixer = X86ClangIncludeFixer::new();
let source = "#include <stdio.h>\n#include \"unused.h\"\nint main() { return 0; }";
let unused = fixer.find_unused_includes(source);
assert!(!unused.is_empty());
}
#[test]
fn test_extract_include_header() {
assert_eq!(
X86ClangIncludeFixer::extract_include_header("#include <stdio.h>"),
Some("stdio.h".into())
);
assert_eq!(
X86ClangIncludeFixer::extract_include_header("#include \"myheader.h\""),
Some("myheader.h".into())
);
assert_eq!(
X86ClangIncludeFixer::extract_include_header("// not an include"),
None
);
}
#[test]
fn test_sort_include_lines_llvm() {
let fixer = X86ClangIncludeFixer::new();
let lines = vec![
"#include <vector>".to_string(),
"#include <algorithm>".to_string(),
"#include \"myheader.h\"".to_string(),
];
let sorted = fixer.sort_include_lines(&lines);
assert!(!sorted.is_empty());
}
#[test]
fn test_fix_source() {
let fixer = X86ClangIncludeFixer::new();
let source = "int main() { printf(\"hi\"); return 0; }";
let (fixed, patches) = fixer.fix_source(source);
assert!(patches > 0);
assert!(fixed.contains("#include <stdio.h>"));
}
#[test]
fn test_move_new() {
let m = X86ClangMove::new();
assert!(m.operations.is_empty());
assert!(!m.dry_run);
}
#[test]
fn test_move_add_operation() {
let mut m = X86ClangMove::new();
m.move_function("myFunc", PathBuf::from("a.c"), PathBuf::from("b.c"));
assert_eq!(m.operations.len(), 1);
assert_eq!(m.operations[0].symbol_name, "myFunc");
}
#[test]
fn test_find_definition_range() {
let m = X86ClangMove::new();
let source = "int foo() {\n return 42;\n}\nint bar() {\n return 0;\n}";
let range = m.find_definition_range(source, "foo");
assert!(range.is_some());
}
#[test]
fn test_find_callers() {
let m = X86ClangMove::new();
let source = "int foo() { return 42; }\nint main() { foo(); }";
let callers = m.find_callers(source, "foo");
assert!(!callers.is_empty());
}
#[test]
fn test_generate_forward_decl_function() {
let m = X86ClangMove::new();
let fwd = m.generate_forward_decl("int foo(int x, float y) {", "foo");
assert!(fwd.contains("foo"));
assert!(fwd.contains(";"));
}
#[test]
fn test_move_extract_definition() {
let m = X86ClangMove::new();
let source = "int foo() {\n return 42;\n}\n";
let range = X86SourceRange::new(1, 1, 3, 1);
let def = m.extract_definition(source, &range);
assert!(def.contains("foo"));
}
#[test]
fn test_move_process_file() {
let m = X86ClangMove::new();
let result = m.process_file("/nonexistent.c");
assert!(result.is_err());
}
#[test]
fn test_rename_new() {
let r = X86ClangRename::new();
assert!(r.operations.is_empty());
assert!(!r.dry_run);
}
#[test]
fn test_rename_add_operation() {
let mut r = X86ClangRename::new();
r.add_rename(
"oldName",
"newName",
X86DocSymbolKind::Function,
vec![PathBuf::from("test.c")],
);
assert_eq!(r.operations.len(), 1);
assert_eq!(r.operations[0].old_name, "oldName");
assert_eq!(r.operations[0].new_name, "newName");
}
#[test]
fn test_find_references() {
let r = X86ClangRename::new();
let source = "void oldFunc() {}\nint main() { oldFunc(); }";
let refs = r.find_references(source, "oldFunc", Path::new("test.c"));
assert!(!refs.is_empty());
}
#[test]
fn test_contains_symbol() {
let r = X86ClangRename::new();
assert!(r.contains_symbol("int myVar = 5;", "myVar"));
assert!(!r.contains_symbol("int myVariable = 5;", "myVar"));
assert!(r.contains_symbol("myVar = 10;", "myVar"));
}
#[test]
fn test_classify_reference() {
assert_eq!(
X86ClangRename::classify_reference("#define FOO 123", "FOO"),
X86SymbolRefType::Definition
);
assert_eq!(
X86ClangRename::classify_reference("int x = FOO + 1;", "FOO"),
X86SymbolRefType::Reference
);
}
#[test]
fn test_replace_symbol_in_source() {
let r = X86ClangRename::new();
let source = "int oldVar = 5;\nint x = oldVar + 1;";
let result = r.replace_symbol_in_source(source, "oldVar", "newVar");
assert!(!result.contains("oldVar"));
assert!(result.contains("newVar"));
}
#[test]
fn test_rename_add_macro() {
let mut r = X86ClangRename::new();
r.add_macro_rename("OLD_MACRO", "NEW_MACRO", vec![PathBuf::from("test.c")]);
assert!(r.operations[0].is_macro);
}
#[test]
fn test_reorder_fields_new() {
let rf = X86ClangReorderFields::new();
assert!(matches!(rf.target_abi, X86TargetABI::LP64));
assert!(rf.analyze_only);
}
#[test]
fn test_type_size_align_lp64() {
let rf = X86ClangReorderFields::new(); assert_eq!(rf.type_size_align("char"), (1, 1));
assert_eq!(rf.type_size_align("int"), (4, 4));
assert_eq!(rf.type_size_align("double"), (8, 8));
assert_eq!(rf.type_size_align("int *"), (8, 8));
}
#[test]
fn test_type_size_align_ilp32() {
let mut rf = X86ClangReorderFields::new();
rf.target_abi = X86TargetABI::ILP32;
assert_eq!(rf.type_size_align("long"), (4, 4));
assert_eq!(rf.type_size_align("int *"), (4, 4));
}
#[test]
fn test_parse_fields() {
let rf = X86ClangReorderFields::new();
let source = "struct Test {\n int a;\n double b;\n char c;\n};";
let fields = rf.parse_fields(source, "Test");
assert_eq!(fields.len(), 3);
assert_eq!(fields[0].name, "a");
assert_eq!(fields[1].name, "b");
assert_eq!(fields[2].name, "c");
}
#[test]
fn test_compute_current_layout() {
let rf = X86ClangReorderFields::new();
let fields = vec![
X86FieldInfo {
name: "a".into(),
field_type: "char".into(),
size_bytes: 1,
alignment_bytes: 1,
offset_bytes: 0,
is_bitfield: false,
bitfield_width: 0,
is_padding: false,
},
X86FieldInfo {
name: "b".into(),
field_type: "int".into(),
size_bytes: 4,
alignment_bytes: 4,
offset_bytes: 0,
is_bitfield: false,
bitfield_width: 0,
is_padding: false,
},
X86FieldInfo {
name: "c".into(),
field_type: "char".into(),
size_bytes: 1,
alignment_bytes: 1,
offset_bytes: 0,
is_bitfield: false,
bitfield_width: 0,
is_padding: false,
},
];
let (size, align, _) = rf.compute_current_layout(&fields);
assert_eq!(size, 12); assert_eq!(align, 4);
}
#[test]
fn test_compute_optimal_layout() {
let rf = X86ClangReorderFields::new();
let fields = vec![
X86FieldInfo {
name: "a".into(),
field_type: "char".into(),
size_bytes: 1,
alignment_bytes: 1,
offset_bytes: 0,
is_bitfield: false,
bitfield_width: 0,
is_padding: false,
},
X86FieldInfo {
name: "b".into(),
field_type: "int".into(),
size_bytes: 4,
alignment_bytes: 4,
offset_bytes: 0,
is_bitfield: false,
bitfield_width: 0,
is_padding: false,
},
X86FieldInfo {
name: "c".into(),
field_type: "double".into(),
size_bytes: 8,
alignment_bytes: 8,
offset_bytes: 0,
is_bitfield: false,
bitfield_width: 0,
is_padding: false,
},
];
let optimal = rf.compute_optimal_layout(&fields);
assert_eq!(optimal[0].field_type, "double");
assert_eq!(optimal[1].field_type, "int");
assert_eq!(optimal[2].field_type, "char");
}
#[test]
fn test_analyze_struct() {
let mut rf = X86ClangReorderFields::new();
let source = "struct Example {\n char a;\n int b;\n char c;\n};";
let analysis = rf.analyze_struct(source, "Example");
assert_eq!(analysis.struct_name, "Example");
assert!(analysis.original_size >= analysis.optimal_size);
}
#[test]
fn test_size_comparison_report() {
let rf = X86ClangReorderFields::new();
let analysis = X86LayoutAnalysis {
struct_name: "Test".into(),
fields: Vec::new(),
original_size: 24,
original_alignment: 8,
optimal_size: 16,
optimal_alignment: 8,
bytes_saved: 8,
padding_before: 8,
padding_after: 0,
target_abi: X86TargetABI::LP64,
};
let report = rf.size_comparison_report(&analysis);
assert!(report.contains("Test"));
assert!(report.contains("24"));
assert!(report.contains("16"));
assert!(report.contains("8"));
}
#[test]
fn test_find_struct_names() {
let rf = X86ClangReorderFields::new();
let source = "struct Foo { int x; };\nstruct Bar { double y; };\nclass Baz { char z; };";
let names = rf.find_struct_names(source);
assert!(names.contains(&"Foo".to_string()));
assert!(names.contains(&"Bar".to_string()));
assert!(names.contains(&"Baz".to_string()));
}
#[test]
fn test_scan_deps_new() {
let sd = X86ScanDeps::new();
assert!(sd.include_paths.contains(&PathBuf::from(".")));
assert!(sd.scan_modules);
assert!(!sd.generate_d_files);
}
#[test]
fn test_scan_deps_add_include_path() {
let mut sd = X86ScanDeps::new();
sd.add_include_path("/usr/include/custom");
assert!(sd
.include_paths
.contains(&PathBuf::from("/usr/include/custom")));
}
#[test]
fn test_scan_deps_add_define() {
let mut sd = X86ScanDeps::new();
sd.add_define("DEBUG", Some("1"));
assert_eq!(sd.defines.len(), 1);
}
#[test]
fn test_compute_hash() {
let sd = X86ScanDeps::new();
let hash = sd.compute_hash("test");
assert_eq!(hash.len(), 16);
assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn test_generate_d_file() {
let sd = X86ScanDeps::new();
let info = X86DependencyInfo {
file: PathBuf::from("main.c"),
dependencies: vec![PathBuf::from("header.h"), PathBuf::from("other.h")],
module_dependencies: Vec::new(),
command_line: Vec::new(),
hash: "abc123".into(),
};
let d = sd.generate_d_file(&info);
assert!(d.contains("main.c:"));
assert!(d.contains("header.h"));
assert!(d.contains("other.h"));
}
#[test]
fn test_generate_compilation_entry() {
let sd = X86ScanDeps::new();
let entry = sd.generate_compilation_entry(
Path::new("test.c"),
Path::new("/project"),
&["-std=c11".into(), "-O2".into()],
);
assert_eq!(entry.directory, "/project");
assert_eq!(entry.file, "test.c");
assert!(!entry.arguments.is_empty());
}
#[test]
fn test_scan_module_imports() {
let sd = X86ScanDeps::new();
let source = "import std.core;\nexport import my.module;\nimport other;";
let modules = sd.scan_module_imports(source);
assert_eq!(modules.len(), 3);
assert!(modules[0].is_interface == false);
assert!(modules[1].is_interface == true);
}
#[test]
fn test_diag_tool_new() {
let dt = X86DiagTool::new();
assert!(!dt.flags.is_empty());
assert!(!dt.groups.is_empty());
}
#[test]
fn test_diag_tool_search_flags() {
let dt = X86DiagTool::new();
let results = dt.search_flags("unused");
assert!(!results.is_empty());
assert!(results.iter().any(|f| f.name.contains("unused")));
}
#[test]
fn test_diag_tool_get_flag_doc() {
let dt = X86DiagTool::new();
let doc = dt.get_flag_doc("-Wunused-variable");
assert!(doc.is_some());
}
#[test]
fn test_diag_tool_enable_disable_warning() {
let mut dt = X86DiagTool::new();
let before = dt.list_enabled_warnings().len();
dt.disable_warning("-Wreturn-type");
let after_disable = dt.list_enabled_warnings().len();
assert!(after_disable < before);
dt.enable_warning("-Wreturn-type");
let after_enable = dt.list_enabled_warnings().len();
assert_eq!(after_enable, before);
}
#[test]
fn test_diag_tool_list_enabled_disabled() {
let dt = X86DiagTool::new();
let enabled = dt.list_enabled_warnings();
let disabled = dt.list_disabled_warnings();
assert!(!enabled.is_empty());
assert!(!disabled.is_empty());
assert_eq!(enabled.len() + disabled.len(), dt.flags.len());
}
#[test]
fn test_diag_tool_count_by_group() {
let dt = X86DiagTool::new();
let counts = dt.count_by_group();
assert!(!counts.is_empty());
}
#[test]
fn test_diag_tool_browse_groups() {
let dt = X86DiagTool::new();
let browser = dt.browse_groups();
assert!(browser.contains("-Wunused"));
assert!(browser.contains("ON"));
}
#[test]
fn test_diag_tool_warning_summary() {
let dt = X86DiagTool::new();
let summary = dt.warning_summary();
assert!(summary.contains("Warning"));
assert!(summary.contains("total"));
}
#[test]
fn test_diag_category_display() {
assert_eq!(X86DiagCategory::Warning.to_string(), "warning");
assert_eq!(X86DiagCategory::Error.to_string(), "error");
assert_eq!(X86DiagCategory::Ignored.to_string(), "ignored");
}
#[test]
fn test_source_range_display() {
let range = X86SourceRange::new(1, 5, 10, 20);
assert_eq!(range.to_string(), "1:5-10:20");
}
#[test]
fn test_replacement_display() {
let repl = X86Replacement::new("f.c", 10, 3, "abc", "fix");
let s = repl.to_string();
assert!(s.contains("f.c"));
assert!(s.contains("abc"));
}
#[test]
fn test_namespace_decl_type_display() {
assert_eq!(X86NamespaceDeclType::Function.to_string(), "function");
assert_eq!(
X86NamespaceDeclType::TemplateClass.to_string(),
"template class"
);
assert_eq!(X86NamespaceDeclType::EnumClass.to_string(), "enum class");
}
#[test]
fn test_doc_symbol_kind_display() {
assert_eq!(X86DocSymbolKind::Function.to_string(), "function");
assert_eq!(X86DocSymbolKind::Constructor.to_string(), "constructor");
assert_eq!(X86DocSymbolKind::Destructor.to_string(), "destructor");
}
#[test]
fn test_namespace_component_display() {
let c = X86NamespaceComponent::new("foo");
assert_eq!(c.to_string(), "foo");
let anon = X86NamespaceComponent::anonymous();
assert_eq!(anon.to_string(), "<anonymous>");
}
#[test]
fn test_target_abi_display() {
assert_eq!(X86TargetABI::LP64.to_string(), "LP64");
assert_eq!(X86TargetABI::ILP32.to_string(), "ILP32");
assert_eq!(X86TargetABI::LLP64.to_string(), "LLP64");
}
#[test]
fn test_include_fixer_custom_sort_empty() {
let mut fixer = X86ClangIncludeFixer::new();
fixer.sort_style = X86IncludeSortStyle::Custom;
fixer.custom_order = vec!["stdio".into(), "stdlib".into()];
let lines = vec!["#include <vector>".into(), "#include <stdio.h>".into()];
let sorted = fixer.sort_include_lines(&lines);
assert!(!sorted.is_empty());
}
#[test]
fn test_include_fixer_google_style() {
let mut fixer = X86ClangIncludeFixer::new();
fixer.sort_style = X86IncludeSortStyle::Google;
let lines = vec![
"#include <vector>".into(),
"#include <string.h>".into(),
"#include <algorithm>".into(),
"#include \"myheader.h\"".into(),
];
let sorted = fixer.sort_include_lines(&lines);
assert!(!sorted.is_empty());
}
#[test]
fn test_reorder_fields_parse_bitfield() {
let rf = X86ClangReorderFields::new();
let source = "struct Flags {\n unsigned int a : 1;\n unsigned int b : 3;\n};";
let fields = rf.parse_fields(source, "Flags");
assert_eq!(fields.len(), 2);
assert!(fields[0].is_bitfield);
}
#[test]
fn test_reorder_fields_parse_empty_struct() {
let rf = X86ClangReorderFields::new();
let source = "struct Empty {\n};";
let fields = rf.parse_fields(source, "Empty");
assert!(fields.is_empty());
}
#[test]
fn test_reorder_fields_ilp32_pointers() {
let mut rf = X86ClangReorderFields::new();
rf.target_abi = X86TargetABI::ILP32;
assert_eq!(rf.type_size_align("void *"), (4, 4));
}
#[test]
fn test_reorder_fields_llp64_long() {
let mut rf = X86ClangReorderFields::new();
rf.target_abi = X86TargetABI::LLP64;
assert_eq!(rf.type_size_align("long"), (4, 4));
assert_eq!(rf.type_size_align("long long"), (8, 8));
}
#[test]
fn test_scan_deps_recursive_depth_limit() {
let mut sd = X86ScanDeps::new();
let path = Path::new("/nonexistent/deep/file.c");
let result = sd.scan_file(path);
assert!(result.is_err());
}
#[test]
fn test_diag_tool_refresh_state() {
let mut dt = X86DiagTool::new();
let initial_enabled = dt.enabled_warnings.len();
dt.flags.first_mut().unwrap().enabled_by_default = false;
dt.refresh_enabled_state();
let after = dt.enabled_warnings.len();
assert_eq!(after, initial_enabled - 1);
}
#[test]
fn test_change_namespace_process_nonexistent_file() {
let mut cn = X86ClangChangeNamespace::new();
let result = cn.process_file("/nonexistent/ns_test.c");
assert!(result.is_err());
}
#[test]
fn test_rename_process_nonexistent_file() {
let r = X86ClangRename::new();
let result = r.process_file("/nonexistent/rename_test.c");
assert!(result.is_err());
}
#[test]
fn test_reorder_fields_process_nonexistent_file() {
let mut rf = X86ClangReorderFields::new();
let result = rf.process_file("/nonexistent/reorder_test.c");
assert!(result.is_err());
}
#[test]
fn test_scan_deps_scan_nonexistent_file() {
let mut sd = X86ScanDeps::new();
let result = sd.scan_file("/nonexistent/dep_test.c");
assert!(result.is_err());
}
#[test]
fn test_diag_tool_analyze_nonexistent_file() {
let dt = X86DiagTool::new();
let result = dt.analyze_file("/nonexistent/diag_test.c");
assert!(result.is_err());
}
#[test]
fn test_yaml_parse_with_comments() {
let yaml = r#"# This is a comment
---
MainSourceFile: 'test.c'
Replacements:
# Another comment
- FilePath: 'test.c'
Offset: 10
Length: 5
ReplacementText: 'hello'
"#;
let mut ar = X86ClangApplyReplacements::new();
let count = ar
.parse_yaml_replacements(yaml, Path::new("f.yaml"))
.unwrap();
assert_eq!(count, 1);
}
#[test]
fn test_yaml_parse_empty_replacements() {
let yaml = "---\nMainSourceFile: 'test.c'\nReplacements:\n";
let mut ar = X86ClangApplyReplacements::new();
let count = ar
.parse_yaml_replacements(yaml, Path::new("f.yaml"))
.unwrap();
assert_eq!(count, 0);
}
#[test]
fn test_rename_partial_word_not_replaced() {
let r = X86ClangRename::new();
assert!(!r.contains_symbol("int myVariable = 5;", "myVar"));
}
#[test]
fn test_rename_word_boundary_underscore() {
let r = X86ClangRename::new();
assert!(!r.contains_symbol("int _myVar = 5;", "myVar"));
}
#[test]
fn test_doc_extract_with_deprecated() {
let source = "/// @deprecated Use newFunc instead\n/// @since 2.0\nvoid oldFunc() {}";
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source(source, Path::new("test.c"))
.unwrap();
assert!(!comments.is_empty());
assert!(comments[0].deprecated.is_some());
assert_eq!(comments[0].since.as_deref(), Some("2.0"));
}
#[test]
fn test_doc_extract_with_exceptions() {
let source = "/**\n * @throws std::runtime_error on failure\n * @throws std::bad_alloc on OOM\n */\nvoid func() {}";
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source(source, Path::new("test.c"))
.unwrap();
assert!(!comments.is_empty());
assert_eq!(comments[0].exceptions.len(), 2);
}
#[test]
fn test_scan_deps_generate_compile_commands_empty() {
let sd = X86ScanDeps::new();
let json = sd.generate_compile_commands_json();
assert!(json.contains("[]"));
}
#[test]
fn test_scan_deps_scan_directory_nonexistent() {
let mut sd = X86ScanDeps::new();
let result = sd.scan_directory("/nonexistent/dir", &["c", "cpp"]);
assert!(result.is_err());
}
#[test]
fn test_apply_replacements_stats_default() {
let stats = X86ApplyReplacementsStats::default();
assert_eq!(stats.files_processed, 0);
assert_eq!(stats.replacements_loaded, 0);
assert_eq!(stats.replacements_applied, 0);
assert_eq!(stats.conflicts_detected, 0);
}
#[test]
fn test_apply_replacements_config_default() {
let config = X86ApplyReplacementsConfig::default();
assert!(!config.dry_run);
assert!(config.deduplicate);
assert!(config.detect_conflicts);
assert!(config.format);
assert!(matches!(config.style, X86ReplacementFileStyle::YAML));
}
#[test]
fn test_replacement_line_col_to_offset() {
let source = "line1\nline2\nline3\n";
let off = X86Replacement::line_col_to_offset(source, 2, 3);
assert!(off > 5);
}
#[test]
fn test_replacement_apply_at_start() {
let repl = X86Replacement::new("f.c", 0, 0, "prefix", "add prefix");
let result = repl.apply("hello world").unwrap();
assert_eq!(result, "prefixhello world");
}
#[test]
fn test_replacement_apply_at_end() {
let source = "hello";
let repl = X86Replacement::new("f.c", 5, 0, " world", "append");
let result = repl.apply(source).unwrap();
assert_eq!(result, "hello world");
}
#[test]
fn test_replacement_apply_out_of_bounds() {
let repl = X86Replacement::new("f.c", 100, 5, "x", "bad");
let result = repl.apply("hello");
assert!(result.is_err());
}
#[test]
fn test_replacement_conflicts_adjacent() {
let a = X86Replacement::new("f.c", 5, 5, "A", "");
let b = X86Replacement::new("f.c", 10, 5, "B", "");
assert!(a.conflicts_with(&b));
}
#[test]
fn test_replacement_no_conflict_gap() {
let a = X86Replacement::new("f.c", 5, 5, "A", "");
let b = X86Replacement::new("f.c", 50, 5, "B", "");
assert!(!a.conflicts_with(&b));
}
#[test]
fn test_replacement_no_conflict_different_file() {
let a = X86Replacement::new("a.c", 10, 5, "A", "");
let b = X86Replacement::new("b.c", 10, 5, "B", "");
assert!(!a.conflicts_with(&b));
}
#[test]
fn test_apply_replacements_files_with_replacements() {
let mut ar = X86ClangApplyReplacements::new();
ar.add_replacement(X86Replacement::new("a.c", 0, 3, "X", ""));
ar.add_replacement(X86Replacement::new("b.c", 0, 3, "Y", ""));
let files = ar.files_with_replacements();
assert_eq!(files.len(), 2);
}
#[test]
fn test_apply_replacements_total_replacements() {
let mut ar = X86ClangApplyReplacements::new();
ar.add_replacement(X86Replacement::new("a.c", 0, 3, "X", ""));
ar.add_replacement(X86Replacement::new("a.c", 4, 3, "Y", ""));
assert_eq!(ar.total_replacements(), 2);
}
#[test]
fn test_apply_replacements_deduplicate_exact() {
let mut ar = X86ClangApplyReplacements::new();
ar.config.deduplicate = true;
ar.add_replacement(X86Replacement::new("a.c", 0, 3, "X", ""));
ar.add_replacement(X86Replacement::new("a.c", 0, 3, "X", ""));
assert_eq!(ar.total_replacements(), 1);
}
#[test]
fn test_resolve_conflicts_keep_last() {
let mut ar = X86ClangApplyReplacements::new();
ar.add_replacement(X86Replacement::new("f.c", 10, 5, "A", ""));
ar.add_replacement(X86Replacement::new("f.c", 12, 5, "B", ""));
ar.detect_all_conflicts();
let resolved = ar.resolve_conflicts(X86ConflictResolution::KeepLast);
assert_eq!(resolved, 1);
}
#[test]
fn test_resolve_conflicts_merge_adjacent() {
let mut ar = X86ClangApplyReplacements::new();
ar.add_replacement(X86Replacement::new("f.c", 5, 3, "AB", ""));
ar.add_replacement(X86Replacement::new("f.c", 8, 3, "CD", ""));
ar.detect_all_conflicts();
let resolved = ar.resolve_conflicts(X86ConflictResolution::Merge);
assert!(resolved > 0);
}
#[test]
fn test_namespace_component_anonymous() {
let c = X86NamespaceComponent::anonymous();
assert!(c.is_anonymous);
assert!(!c.is_inline);
}
#[test]
fn test_namespace_component_is_nested() {
let c = X86NamespaceComponent::new("foo");
assert!(c.is_nested());
let anon = X86NamespaceComponent::anonymous();
assert!(!anon.is_nested());
}
#[test]
fn test_namespace_path_from_str_with_trailing_colons() {
let ns = X86NamespacePath::from_str("::foo::bar::");
assert_eq!(ns.components.len(), 2);
}
#[test]
fn test_namespace_path_parent_of_root() {
let ns = X86NamespacePath::from_str("a");
assert!(ns.parent().is_none());
}
#[test]
fn test_namespace_path_leaf() {
let ns = X86NamespacePath::from_str("a::b::c");
let leaf = ns.leaf().unwrap();
assert_eq!(leaf.name, "c");
}
#[test]
fn test_namespace_path_push() {
let mut ns = X86NamespacePath::from_str("a::b");
ns.push(X86NamespaceComponent::new("c"));
assert_eq!(ns.to_string(), "a::b::c");
}
#[test]
fn test_namespace_decl_type_all_variants() {
let types = [
X86NamespaceDeclType::Function,
X86NamespaceDeclType::Class,
X86NamespaceDeclType::Struct,
X86NamespaceDeclType::Enum,
X86NamespaceDeclType::Variable,
X86NamespaceDeclType::TypeAlias,
X86NamespaceDeclType::UsingDeclaration,
X86NamespaceDeclType::TemplateFunction,
X86NamespaceDeclType::TemplateClass,
X86NamespaceDeclType::EnumClass,
];
for t in &types {
let s = t.to_string();
assert!(!s.is_empty());
}
}
#[test]
fn test_change_namespace_generate_move_patch() {
let mut cn = X86ClangChangeNamespace::new();
cn.set_namespaces("old", "new");
let decl = X86NamespaceDeclaration {
name: "Foo".into(),
qualified_name: "old::Foo".into(),
source_namespace: X86NamespacePath::from_str("old"),
target_namespace: X86NamespacePath::from_str("new"),
decl_type: X86NamespaceDeclType::Class,
file_path: PathBuf::from("test.c"),
range: X86SourceRange::new(1, 1, 1, 10),
};
let patch = cn.generate_move_patch(&decl);
assert!(patch.contains("old"));
assert!(patch.contains("new"));
}
#[test]
fn test_change_namespace_scan_empty_namespace() {
let mut cn = X86ClangChangeNamespace::new();
let ns = X86NamespacePath::empty();
let source = "int globalVar;\nvoid globalFunc() {}\n";
cn.source_cache
.insert(PathBuf::from("test.c"), source.to_string());
let decls = cn
.scan_namespace_contents("test.c", &ns)
.unwrap_or_default();
assert!(!decls.is_empty() || decls.is_empty()); }
#[test]
fn test_classify_declaration_line_enum_class() {
assert_eq!(
X86ClangChangeNamespace::classify_declaration_line("enum class Color { Red, Green }"),
Some(X86NamespaceDeclType::EnumClass)
);
}
#[test]
fn test_classify_declaration_line_typedef() {
assert_eq!(
X86ClangChangeNamespace::classify_declaration_line("typedef int MyInt;"),
Some(X86NamespaceDeclType::TypeAlias)
);
}
#[test]
fn test_classify_declaration_line_using() {
assert_eq!(
X86ClangChangeNamespace::classify_declaration_line("using std::vector;"),
Some(X86NamespaceDeclType::UsingDeclaration)
);
}
#[test]
fn test_extract_decl_name_const_qualified() {
assert_eq!(
X86ClangChangeNamespace::extract_decl_name("const int CONST_VAL;"),
Some("CONST_VAL".into())
);
}
#[test]
fn test_extract_decl_name_static_inline() {
assert_eq!(
X86ClangChangeNamespace::extract_decl_name("static inline int helper(void)"),
Some("helper".into())
);
}
#[test]
fn test_doc_output_format_all_variants() {
let formats = [
X86DocOutputFormat::Markdown,
X86DocOutputFormat::HTML,
X86DocOutputFormat::ManPage,
X86DocOutputFormat::YAML,
X86DocOutputFormat::JSON,
X86DocOutputFormat::RST,
X86DocOutputFormat::PlainText,
];
for _f in &formats {
}
}
#[test]
fn test_doc_generate_rst() {
let mut doc = X86ClangDoc::new();
doc.project_name = "TestProject".into();
doc.comments.push(X86DocComment {
symbol_name: "foo".into(),
symbol_kind: X86DocSymbolKind::Function,
brief: "Test function".into(),
full_text: String::new(),
params: Vec::new(),
return_desc: Some("int".into()),
exceptions: Vec::new(),
see_also: vec!["bar".into()],
related: Vec::new(),
notes: Vec::new(),
warnings: Vec::new(),
preconditions: Vec::new(),
postconditions: Vec::new(),
deprecated: None,
since: None,
authors: Vec::new(),
file_path: PathBuf::from("test.c"),
line: 1,
});
let rst = doc.generate_rst();
assert!(rst.contains("TestProject"));
assert!(rst.contains("foo"));
}
#[test]
fn test_doc_generate_plain_text() {
let mut doc = X86ClangDoc::new();
doc.project_name = "Lib".into();
doc.comments.push(X86DocComment {
symbol_name: "fn".into(),
symbol_kind: X86DocSymbolKind::Function,
brief: "desc".into(),
full_text: String::new(),
params: Vec::new(),
return_desc: None,
exceptions: Vec::new(),
see_also: Vec::new(),
related: Vec::new(),
notes: Vec::new(),
warnings: Vec::new(),
preconditions: Vec::new(),
postconditions: Vec::new(),
deprecated: None,
since: None,
authors: Vec::new(),
file_path: PathBuf::from("test.c"),
line: 1,
});
let txt = doc.generate_plain_text();
assert!(txt.contains("fn"));
assert!(txt.contains("desc"));
}
#[test]
fn test_doc_extract_with_author() {
let source = "/// @author John Doe\n/// @author Jane Smith\nvoid f() {}";
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source(source, Path::new("test.c"))
.unwrap();
assert!(!comments.is_empty());
assert_eq!(comments[0].authors.len(), 2);
}
#[test]
fn test_doc_extract_with_note_and_warning() {
let source = "/// @note This is important\n/// @warning Be careful\nvoid f() {}";
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source(source, Path::new("test.c"))
.unwrap();
assert_eq!(comments[0].notes.len(), 1);
assert_eq!(comments[0].warnings.len(), 1);
}
#[test]
fn test_doc_extract_with_pre_post() {
let source =
"/// @pre x must be positive\n/// @post result is non-negative\nint f(int x) {}";
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source(source, Path::new("test.c"))
.unwrap();
assert_eq!(comments[0].preconditions.len(), 1);
assert_eq!(comments[0].postconditions.len(), 1);
}
#[test]
fn test_doc_extract_with_related() {
let source = "/// @related otherFunc\n/// @related anotherFunc\nvoid f() {}";
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source(source, Path::new("test.c"))
.unwrap();
assert_eq!(comments[0].related.len(), 2);
}
#[test]
fn test_doc_extract_in_out_params() {
let source = "/** @param[in] x input\n * @param[out] y output\n * @param[in,out] z both\n */\nvoid f(int x, int* y, int* z) {}";
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source(source, Path::new("test.c"))
.unwrap();
assert_eq!(comments[0].params.len(), 3);
}
#[test]
fn test_doc_extract_backslash_tags() {
let source =
"/// \\brief Brief desc\n/// \\param x the param\n/// \\return result\nint f(int x) {}";
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source(source, Path::new("test.c"))
.unwrap();
assert!(!comments.is_empty());
}
#[test]
fn test_doc_extract_line_comment_bang() {
let source = "//! Module-level comment\n//! More info\nint x;";
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source(source, Path::new("test.c"))
.unwrap();
assert!(!comments.is_empty());
}
#[test]
fn test_doc_extract_block_comment_bang() {
let source = "/*! Brief description */\nint x;";
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source(source, Path::new("test.c"))
.unwrap();
assert!(!comments.is_empty());
}
#[test]
fn test_doc_classify_constructor() {
let line = "MyClass(int x) : member(x) {";
let kind = X86ClangDoc::classify_symbol_from_line(line);
assert!(matches!(kind, X86DocSymbolKind::Function));
}
#[test]
fn test_doc_extract_symbol_name_long_type() {
let line = "unsigned long long int ullVar;";
let name = X86ClangDoc::extract_symbol_name(line);
assert_eq!(name, "ullVar");
}
#[test]
fn test_doc_escape_html() {
let escaped = X86ClangDoc::escape_html("<tag>&");
assert_eq!(escaped, "<tag>&");
}
#[test]
fn test_include_fixer_custom_sort_empty_order() {
let mut fixer = X86ClangIncludeFixer::new();
fixer.sort_style = X86IncludeSortStyle::Custom;
fixer.custom_order.clear();
let lines = vec![
"#include <vector>".to_string(),
"#include <stdio.h>".to_string(),
];
let sorted = fixer.sort_include_lines(&lines);
assert_eq!(sorted.len(), 2);
}
#[test]
fn test_include_fixer_process_file_result() {
let fixer = X86ClangIncludeFixer::new();
let result = fixer.process_file("/nonexistent.c");
assert!(result.is_err());
}
#[test]
fn test_include_fixer_find_missing_cpp() {
let fixer = X86ClangIncludeFixer::new();
let source = "int main() { std::vector<int> v; std::sort(v.begin(), v.end()); return 0; }";
let missing = fixer.find_missing_includes(source);
assert!(!missing.is_empty());
assert!(missing.iter().any(|h| h == "vector" || h == "algorithm"));
}
#[test]
fn test_include_fixer_find_missing_none() {
let fixer = X86ClangIncludeFixer::new();
let source = "#include <stdio.h>\nint main() { printf(\"hi\"); return 0; }";
let missing = fixer.find_missing_includes(source);
assert!(missing.is_empty());
}
#[test]
fn test_include_fixer_fix_source_add_missing_false() {
let mut fixer = X86ClangIncludeFixer::new();
fixer.add_missing = false;
let source = "int main() { printf(\"hi\"); return 0; }";
let (fixed, _) = fixer.fix_source(source);
assert_eq!(fixed, source);
}
#[test]
fn test_include_fixer_fix_source_remove_unused_false() {
let mut fixer = X86ClangIncludeFixer::new();
fixer.remove_unused = false;
let source = "#include \"unused.h\"\nint main() { return 0; }";
let (fixed, _) = fixer.fix_source(source);
assert!(fixed.contains("#include"));
}
#[test]
fn test_include_fixer_fix_source_sort_false() {
let mut fixer = X86ClangIncludeFixer::new();
fixer.sort_includes = false;
fixer.add_missing = false;
fixer.remove_unused = false;
let source = "int main() { return 0; }";
let (fixed, patches) = fixer.fix_source(source);
assert_eq!(patches, 0);
assert_eq!(fixed, source);
}
#[test]
fn test_include_fixer_extract_include_header_angle() {
assert_eq!(
X86ClangIncludeFixer::extract_include_header("#include <sys/types.h>"),
Some("sys/types.h".into())
);
}
#[test]
fn test_include_fixer_extract_include_header_quote() {
assert_eq!(
X86ClangIncludeFixer::extract_include_header("#include \"my/header.h\""),
Some("my/header.h".into())
);
}
#[test]
fn test_include_fixer_sort_none() {
let mut fixer = X86ClangIncludeFixer::new();
fixer.sort_style = X86IncludeSortStyle::None;
let lines = vec!["#include <b>".into(), "#include <a>".into()];
let sorted = fixer.sort_include_lines(&lines);
assert_eq!(sorted, lines);
}
#[test]
fn test_move_class_operation() {
let mut m = X86ClangMove::new();
m.move_class("MyClass", PathBuf::from("a.cpp"), PathBuf::from("b.cpp"));
assert_eq!(m.operations.len(), 1);
assert!(matches!(
m.operations[0].symbol_kind,
X86DocSymbolKind::Class
));
}
#[test]
fn test_move_find_definition_range_class() {
let m = X86ClangMove::new();
let source = "class Foo {\npublic:\n int x;\n};\n";
let range = m.find_definition_range(source, "Foo");
assert!(range.is_some());
}
#[test]
fn test_move_find_definition_range_not_found() {
let m = X86ClangMove::new();
let source = "int bar() { return 0; }";
let range = m.find_definition_range(source, "foo");
assert!(range.is_none());
}
#[test]
fn test_move_find_callers_no_callers() {
let m = X86ClangMove::new();
let source = "int foo() { return 42; }";
let callers = m.find_callers(source, "foo");
assert!(callers.is_empty());
}
#[test]
fn test_move_find_callers_skips_comments() {
let m = X86ClangMove::new();
let source = "// foo is used here\nint main() { foo(); }";
let callers = m.find_callers(source, "foo");
assert!(callers.len() >= 1);
}
#[test]
fn test_move_extract_dependencies() {
let mut m = X86ClangMove::new();
let source = "#include <stdio.h>\n#include <stdlib.h>\n\nint foo() { return 0; }";
let deps = m.extract_dependencies(source, "foo");
assert_eq!(deps.len(), 2);
}
#[test]
fn test_move_generate_forward_decl_class() {
let m = X86ClangMove::new();
let fwd = m.generate_forward_decl("class MyClass {", "MyClass");
assert!(fwd.contains("MyClass"));
assert!(fwd.contains(";"));
}
#[test]
fn test_move_generate_forward_decl_struct() {
let m = X86ClangMove::new();
let fwd = m.generate_forward_decl("struct Data {", "Data");
assert!(fwd.contains("Data"));
}
#[test]
fn test_move_dry_run() {
let mut m = X86ClangMove::new();
m.dry_run = true;
m.move_function("foo", PathBuf::from("a.c"), PathBuf::from("b.c"));
let results = m.execute_all();
assert!(!results.is_empty());
assert!(results[0].success);
}
#[test]
fn test_move_execute_nonexistent_source() {
let mut m = X86ClangMove::new();
m.dry_run = false;
m.move_function(
"foo",
PathBuf::from("/nonexistent/src.c"),
PathBuf::from("/tmp/out.c"),
);
let results = m.execute_all();
assert!(!results[0].success);
assert!(results[0].error.is_some());
}
#[test]
fn test_move_load_source_cache() {
let mut m = X86ClangMove::new();
let result = m.load_source(Path::new("/nonexistent.c"));
assert!(result.is_err());
}
#[test]
fn test_rename_execute_all_dry() {
let mut r = X86ClangRename::new();
r.dry_run = true;
r.add_rename(
"old",
"new",
X86DocSymbolKind::Variable,
vec![PathBuf::from("test.c")],
);
let results = r.execute_all();
assert!(!results.is_empty());
}
#[test]
fn test_rename_symbol_ref_type_all_variants() {
let types = [
X86SymbolRefType::Definition,
X86SymbolRefType::Declaration,
X86SymbolRefType::Call,
X86SymbolRefType::Reference,
X86SymbolRefType::TemplateSpecialization,
X86SymbolRefType::MacroExpansion,
X86SymbolRefType::IncludeDirective,
];
assert_eq!(types.len(), 7);
}
#[test]
fn test_rename_classify_template_specialization() {
assert_eq!(
X86ClangRename::classify_reference("template<> class Vec<int> {", "Vec"),
X86SymbolRefType::TemplateSpecialization
);
}
#[test]
fn test_rename_classify_call() {
assert_eq!(
X86ClangRename::classify_reference("int x = foo(1, 2);", "foo"),
X86SymbolRefType::Call
);
}
#[test]
fn test_rename_classify_declaration() {
assert_eq!(
X86ClangRename::classify_reference("extern int globalVar;", "globalVar"),
X86SymbolRefType::Declaration
);
}
#[test]
fn test_rename_replace_not_inside_line_comment() {
let r = X86ClangRename::new();
let source = "// This is oldVar\nint oldVar = 5;";
let result = r.replace_symbol_in_source(source, "oldVar", "newVar");
assert!(result.contains("// This is oldVar"));
assert!(result.contains("int newVar = 5;"));
}
#[test]
fn test_rename_find_references_in_macro() {
let r = X86ClangRename::new();
let source = "#define CALL_FUNC() oldFunc()\nvoid oldFunc() {}";
let refs = r.find_references(source, "oldFunc", Path::new("test.c"));
assert!(!refs.is_empty());
}
#[test]
fn test_rename_handle_overloaded_flag() {
let mut r = X86ClangRename::new();
r.add_rename("overloaded", "renamed", X86DocSymbolKind::Function, vec![]);
assert!(r.operations[0].handle_overloads);
}
#[test]
fn test_rename_handle_template_specs_flag() {
let mut r = X86ClangRename::new();
r.add_rename("Templ", "RenamedTempl", X86DocSymbolKind::Template, vec![]);
assert!(r.operations[0].handle_template_specs);
}
#[test]
fn test_rename_execute_nonexistent_file() {
let mut r = X86ClangRename::new();
r.add_rename(
"foo",
"bar",
X86DocSymbolKind::Function,
vec![PathBuf::from("/nonexistent/test.c")],
);
let results = r.execute_all();
assert_eq!(results[0].files_changed, 0);
}
#[test]
fn test_reorder_fields_parse_nested_struct() {
let rf = X86ClangReorderFields::new();
let source = "struct Outer {\n struct Inner { int x; } inner;\n};\n";
let fields = rf.parse_fields(source, "Outer");
assert_eq!(fields.len(), 1);
}
#[test]
fn test_reorder_fields_parse_with_methods() {
let rf = X86ClangReorderFields::new();
let source = "struct WithMethods {\n int x;\n void foo() {}\n double y;\n};\n";
let fields = rf.parse_fields(source, "WithMethods");
assert_eq!(fields.len(), 2);
}
#[test]
fn test_reorder_fields_parse_bitfield_widths() {
let rf = X86ClangReorderFields::new();
let source = "struct Bits {\n unsigned int a : 4;\n unsigned int b : 8;\n};\n";
let fields = rf.parse_fields(source, "Bits");
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].bitfield_width, 4);
assert_eq!(fields[1].bitfield_width, 8);
}
#[test]
fn test_reorder_fields_type_size_align_long_double_lp64() {
let rf = X86ClangReorderFields::new();
assert_eq!(rf.type_size_align("long double"), (16, 16));
}
#[test]
fn test_reorder_fields_type_size_align_long_double_ilp32() {
let mut rf = X86ClangReorderFields::new();
rf.target_abi = X86TargetABI::ILP32;
assert_eq!(rf.type_size_align("long double"), (12, 4));
}
#[test]
fn test_reorder_fields_type_size_align_long_double_llp64() {
let mut rf = X86ClangReorderFields::new();
rf.target_abi = X86TargetABI::LLP64;
assert_eq!(rf.type_size_align("long double"), (8, 8));
}
#[test]
fn test_reorder_fields_type_size_align_int128() {
let rf = X86ClangReorderFields::new();
assert_eq!(rf.type_size_align("__int128"), (16, 16));
assert_eq!(rf.type_size_align("__int128_t"), (16, 16));
}
#[test]
fn test_reorder_fields_type_size_align_const() {
let rf = X86ClangReorderFields::new();
assert_eq!(rf.type_size_align("const int"), (4, 4));
}
#[test]
fn test_reorder_fields_analyze_no_savings() {
let mut rf = X86ClangReorderFields::new();
let source = "struct Optimal {\n double a;\n int b;\n char c;\n};\n";
let analysis = rf.analyze_struct(source, "Optimal");
assert_eq!(analysis.bytes_saved, 0);
}
#[test]
fn test_reorder_fields_generate_reorder_patch() {
let rf = X86ClangReorderFields::new();
let analysis = X86LayoutAnalysis {
struct_name: "Test".into(),
fields: vec![
X86FieldInfo {
name: "a".into(),
field_type: "char".into(),
size_bytes: 1,
alignment_bytes: 1,
offset_bytes: 0,
is_bitfield: false,
bitfield_width: 0,
is_padding: false,
},
X86FieldInfo {
name: "b".into(),
field_type: "int".into(),
size_bytes: 4,
alignment_bytes: 4,
offset_bytes: 0,
is_bitfield: false,
bitfield_width: 0,
is_padding: false,
},
],
original_size: 8,
original_alignment: 4,
optimal_size: 8,
optimal_alignment: 4,
bytes_saved: 0,
padding_before: 3,
padding_after: 0,
target_abi: X86TargetABI::LP64,
};
let patch = rf.generate_reorder_patch(&analysis);
assert!(patch.contains("Test"));
}
#[test]
fn test_reorder_fields_find_struct_names_class() {
let rf = X86ClangReorderFields::new();
let source = "class MyClass { int x; };\nclass TemplateClass;\n";
let names = rf.find_struct_names(source);
assert!(names.contains(&"MyClass".to_string()));
}
#[test]
fn test_reorder_fields_respect_max_alignment() {
let mut rf = X86ClangReorderFields::new();
rf.max_alignment = 4;
let fields = vec![
X86FieldInfo {
name: "a".into(),
field_type: "double".into(),
size_bytes: 8,
alignment_bytes: 8,
offset_bytes: 0,
is_bitfield: false,
bitfield_width: 0,
is_padding: false,
},
X86FieldInfo {
name: "b".into(),
field_type: "int".into(),
size_bytes: 4,
alignment_bytes: 4,
offset_bytes: 0,
is_bitfield: false,
bitfield_width: 0,
is_padding: false,
},
];
let (size, align, _) = rf.compute_current_layout(&fields);
assert_eq!(align, 4);
}
#[test]
fn test_scan_deps_compute_hash_different_strings() {
let sd = X86ScanDeps::new();
let h1 = sd.compute_hash("hello");
let h2 = sd.compute_hash("world");
assert_ne!(h1, h2);
}
#[test]
fn test_scan_deps_compute_hash_deterministic() {
let sd = X86ScanDeps::new();
let h1 = sd.compute_hash("test");
let h2 = sd.compute_hash("test");
assert_eq!(h1, h2);
}
#[test]
fn test_scan_deps_add_define_no_value() {
let mut sd = X86ScanDeps::new();
sd.add_define("NDEBUG", None);
assert_eq!(sd.defines.len(), 1);
assert_eq!(sd.defines[0].0, "NDEBUG");
assert_eq!(sd.defines[0].1, None);
}
#[test]
fn test_scan_deps_resolve_include_relative() {
let sd = X86ScanDeps::new();
let result = sd.resolve_include(Path::new("/tmp/file.c"), "nonexistent.h");
let _ = result;
}
#[test]
fn test_scan_deps_generate_d_file_empty_deps() {
let sd = X86ScanDeps::new();
let info = X86DependencyInfo {
file: PathBuf::from("main.c"),
dependencies: Vec::new(),
module_dependencies: Vec::new(),
command_line: Vec::new(),
hash: String::new(),
};
let d = sd.generate_d_file(&info);
assert!(d.contains("main.c:"));
}
#[test]
fn test_scan_deps_generate_compile_commands_multiple_entries() {
let mut sd = X86ScanDeps::new();
sd.compilation_entries.push(X86CompileCommandEntry {
directory: "/proj".into(),
file: "a.c".into(),
command: "clang -c a.c".into(),
arguments: vec!["clang".into(), "-c".into(), "a.c".into()],
output: Some("a.o".into()),
});
sd.compilation_entries.push(X86CompileCommandEntry {
directory: "/proj".into(),
file: "b.c".into(),
command: "clang -c b.c".into(),
arguments: vec!["clang".into(), "-c".into(), "b.c".into()],
output: Some("b.o".into()),
});
let json = sd.generate_compile_commands_json();
assert!(json.contains("a.c"));
assert!(json.contains("b.c"));
}
#[test]
fn test_scan_deps_scan_module_imports_export() {
let sd = X86ScanDeps::new();
let source = "export module my.module;\nexport import std.core;\n";
let modules = sd.scan_module_imports(source);
assert_eq!(modules.len(), 1);
assert!(modules[0].is_interface);
}
#[test]
fn test_diag_flag_all_fields() {
let dt = X86DiagTool::new();
let flag = &dt.flags[0];
assert!(!flag.name.is_empty());
assert!(flag.flag.starts_with('-'));
let _ = flag.category;
let _ = flag.default_severity;
}
#[test]
fn test_diag_severity_level_ordering() {
assert!(X86DiagSeverityLevel::Ignored < X86DiagSeverityLevel::Warning);
assert!(X86DiagSeverityLevel::Warning < X86DiagSeverityLevel::Error);
assert!(X86DiagSeverityLevel::Error < X86DiagSeverityLevel::Fatal);
}
#[test]
fn test_diag_severity_level_display() {
assert_eq!(X86DiagSeverityLevel::Ignored.to_string(), "ignored");
assert_eq!(X86DiagSeverityLevel::Note.to_string(), "note");
assert_eq!(X86DiagSeverityLevel::Remark.to_string(), "remark");
assert_eq!(X86DiagSeverityLevel::Warning.to_string(), "warning");
assert_eq!(X86DiagSeverityLevel::Error.to_string(), "error");
assert_eq!(X86DiagSeverityLevel::Fatal.to_string(), "fatal");
}
#[test]
fn test_diag_tool_search_case_insensitive() {
let dt = X86DiagTool::new();
let results_lower = dt.search_flags("unused");
let results_upper = dt.search_flags("UNUSED");
assert_eq!(results_lower.len(), results_upper.len());
}
#[test]
fn test_diag_tool_search_description() {
let dt = X86DiagTool::new();
let results = dt.search_flags("implicit conversion");
assert!(!results.is_empty());
}
#[test]
fn test_diag_tool_enable_nonexistent_flag() {
let mut dt = X86DiagTool::new();
let before = dt.flags.len();
dt.enable_warning("-W-nonexistent-flag");
assert_eq!(dt.flags.len(), before);
}
#[test]
fn test_diag_tool_disable_nonexistent_flag() {
let mut dt = X86DiagTool::new();
let before = dt.flags.len();
dt.disable_warning("-W-nonexistent-flag");
assert_eq!(dt.flags.len(), before);
}
#[test]
fn test_diag_tool_enable_without_prefix() {
let mut dt = X86DiagTool::new();
dt.enable_warning("unused-variable");
let flag = dt.get_flag_doc("unused-variable");
assert!(flag.is_some());
assert!(flag.unwrap().enabled_by_default);
}
#[test]
fn test_diag_tool_disable_without_prefix() {
let mut dt = X86DiagTool::new();
dt.disable_warning("unused-variable");
let flag = dt.get_flag_doc("unused-variable");
assert!(flag.is_some());
assert!(!flag.unwrap().enabled_by_default);
}
#[test]
fn test_diag_tool_analyze_file_with_patterns() {
let dt = X86DiagTool::new();
let result = dt.analyze_file("/nonexistent.c");
assert!(result.is_err());
}
#[test]
fn test_full_tools_run_all_no_files() {
let mut tools = X86ClangToolsFull::new();
let result = tools.run_all();
assert_eq!(result.total_operations(), 0);
}
#[test]
fn test_full_tools_run_all_with_nonexistent_files() {
let mut tools = X86ClangToolsFull::new();
tools.add_file("/nonexistent/test.c");
let result = tools.run_all();
assert!(result.total_operations() == 0);
}
#[test]
fn test_tools_full_result_default() {
let result = X86ToolsFullResult::new();
assert_eq!(result.total_operations(), 0);
}
#[test]
fn test_source_range_from_locations() {
let start = X86SourceLocation::new(1, 5);
let end = X86SourceLocation::new(10, 20);
let range = X86SourceRange::from_locations(start, end);
assert_eq!(range.start_line, 1);
assert_eq!(range.end_col, 20);
}
#[test]
fn test_source_range_overlaps_edge_case_same_line() {
let a = X86SourceRange::new(5, 10, 5, 20);
let b = X86SourceRange::new(5, 15, 5, 25);
assert!(a.overlaps(&b));
}
#[test]
fn test_source_range_overlaps_adjacent_disjoint() {
let a = X86SourceRange::new(5, 1, 5, 10);
let b = X86SourceRange::new(5, 11, 5, 20);
let _ = a.overlaps(&b);
}
#[test]
fn test_apply_replacements_with_empty_replacement_list() {
let mut ar = X86ClangApplyReplacements::new();
let result = ar.process_file("/nonexistent.c");
assert!(result.is_err());
}
#[test]
fn test_change_namespace_commit_no_changes() {
let cn = X86ClangChangeNamespace::new();
assert_eq!(cn.commit_changes().unwrap(), 0);
}
#[test]
fn test_change_namespace_is_symbol_use() {
assert!(X86ClangChangeNamespace::is_symbol_use(
"int x = myVar + 1;",
"myVar"
));
assert!(!X86ClangChangeNamespace::is_symbol_use(
"class myVar {",
"myVar"
));
assert!(!X86ClangChangeNamespace::is_symbol_use(
"// myVar comment",
"myVar"
));
assert!(!X86ClangChangeNamespace::is_symbol_use(
"#define myVar",
"myVar"
));
}
#[test]
fn test_doc_extract_empty_source() {
let mut doc = X86ClangDoc::new();
let comments = doc.extract_from_source("", Path::new("empty.c")).unwrap();
assert!(comments.is_empty());
}
#[test]
fn test_doc_extract_no_doc_comments() {
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source("int x = 5;\nint y = 10;", Path::new("test.c"))
.unwrap();
assert!(comments.is_empty());
}
#[test]
fn test_doc_generate_all_markdown_no_comments() {
let doc = X86ClangDoc::new();
let md = doc.generate_markdown();
assert!(md.contains("API Reference"));
}
#[test]
fn test_doc_generate_html_no_comments() {
let doc = X86ClangDoc::new();
let html = doc.generate_html();
assert!(html.contains("<!DOCTYPE html>"));
}
#[test]
fn test_doc_write_to_file_nonexistent_dir() {
let doc = X86ClangDoc::new();
let result = doc.write_to_file("/tmp/nonexistent_dir_12345/output.md");
let _ = result;
}
#[test]
fn test_include_fixer_default_mappings_count() {
let fixer = X86ClangIncludeFixer::new();
assert!(fixer.include_mappings.len() > 10);
}
#[test]
fn test_include_fixer_custom_priority_no_match() {
let mut fixer = X86ClangIncludeFixer::new();
fixer.custom_order = vec!["stdio".into()];
let priority = fixer.custom_priority("#include <vector>");
assert_eq!(priority, 1); }
#[test]
fn test_include_fixer_custom_priority_match() {
let mut fixer = X86ClangIncludeFixer::new();
fixer.custom_order = vec!["stdio".into(), "stdlib".into()];
let priority = fixer.custom_priority("#include <stdio.h>");
assert_eq!(priority, 0);
}
#[test]
fn test_move_operation_clone() {
let op = X86ClangMoveOperation {
symbol_name: "foo".into(),
symbol_kind: X86DocSymbolKind::Function,
source_file: PathBuf::from("a.c"),
target_file: PathBuf::from("b.c"),
source_range: X86SourceRange::new(1, 1, 10, 1),
update_callers: true,
add_forward_decl: true,
add_missing_includes: true,
};
let cloned = op.clone();
assert_eq!(cloned.symbol_name, "foo");
}
#[test]
fn test_move_result_clone() {
let result = X86ClangMoveResult {
operation: X86ClangMoveOperation {
symbol_name: "foo".into(),
symbol_kind: X86DocSymbolKind::Function,
source_file: PathBuf::from("a.c"),
target_file: PathBuf::from("b.c"),
source_range: X86SourceRange::new(1, 1, 1, 1),
update_callers: true,
add_forward_decl: false,
add_missing_includes: false,
},
callers_updated: 3,
includes_added: 1,
success: true,
error: None,
};
let cloned = result.clone();
assert!(cloned.success);
}
#[test]
fn test_rename_result_all_fields() {
let result = X86RenameResult {
old_name: "old".into(),
new_name: "new".into(),
files_changed: 2,
references_updated: 10,
error: None,
};
assert_eq!(result.files_changed, 2);
assert_eq!(result.references_updated, 10);
}
#[test]
fn test_rename_operation_with_qualification() {
let mut op = X86RenameOperation {
old_name: "old".into(),
new_name: "new".into(),
symbol_kind: X86DocSymbolKind::Function,
file_paths: vec![],
is_macro: false,
handle_overloads: true,
handle_template_specs: true,
qualification: Some("ns::".into()),
};
assert!(op.qualification.is_some());
}
#[test]
fn test_reorder_fields_same_size_optimal() {
let mut rf = X86ClangReorderFields::new();
let source = "struct Sorted {\n double a;\n long long b;\n int c;\n short d;\n char e;\n};\n";
let analysis = rf.analyze_struct(source, "Sorted");
assert_eq!(analysis.bytes_saved, 0);
}
#[test]
fn test_reorder_fields_suboptimal_ordering() {
let mut rf = X86ClangReorderFields::new();
let source =
"struct Suboptimal {\n char a;\n double b;\n char c;\n int d;\n};\n";
let analysis = rf.analyze_struct(source, "Suboptimal");
assert!(analysis.bytes_saved > 0);
}
#[test]
fn test_scan_deps_write_d_file_nonexistent_path() {
let sd = X86ScanDeps::new();
let info = X86DependencyInfo {
file: PathBuf::from("main.c"),
dependencies: Vec::new(),
module_dependencies: Vec::new(),
command_line: Vec::new(),
hash: String::new(),
};
let result = sd.write_d_file("/nonexistent/deep/path/file.d", &info);
assert!(result.is_err());
}
#[test]
fn test_diag_tool_group_parent() {
let dt = X86DiagTool::new();
let group = dt.groups.iter().find(|g| g.parent_group.is_some());
assert!(group.is_some());
}
#[test]
fn test_diag_tool_all_flags_have_category() {
let dt = X86DiagTool::new();
for flag in &dt.flags {
let _ = flag.category;
let _ = flag.description;
}
}
#[test]
fn test_yaml_parse_value_with_spaces() {
let line = " FilePath: 'some/path with spaces/file.c'";
let val = X86ClangApplyReplacements::parse_yaml_value(line, "FilePath");
assert_eq!(val, Some("some/path with spaces/file.c".into()));
}
#[test]
fn test_yaml_parse_value_double_quoted() {
let line = " FilePath: \"path/to/file.c\"";
let val = X86ClangApplyReplacements::parse_yaml_value(line, "FilePath");
assert_eq!(val, Some("path/to/file.c".into()));
}
#[test]
fn test_json_parse_extract_string() {
let obj = "{\"FilePath\": \"test.c\", \"Offset\": 42}";
let val = X86ClangApplyReplacements::extract_json_string(obj, "FilePath");
assert_eq!(val, Some("test.c".into()));
}
#[test]
fn test_json_parse_extract_int() {
let obj = "{\"Offset\": 123, \"Length\": 5}";
let val = X86ClangApplyReplacements::extract_json_int(obj, "Offset");
assert_eq!(val, Some(123));
}
#[test]
fn test_json_parse_missing_key() {
let obj = "{\"OtherField\": \"value\"}";
let val = X86ClangApplyReplacements::extract_json_string(obj, "FilePath");
assert_eq!(val, None);
}
#[test]
fn test_doc_param_direction_display() {
assert_eq!(X86DocParamDirection::In.to_string(), "in");
assert_eq!(X86DocParamDirection::Out.to_string(), "out");
assert_eq!(X86DocParamDirection::InOut.to_string(), "in,out");
}
#[test]
fn test_replacement_file_style() {
assert!(matches!(
X86ReplacementFileStyle::YAML,
X86ReplacementFileStyle::YAML
));
assert!(matches!(
X86ReplacementFileStyle::JSON,
X86ReplacementFileStyle::JSON
));
}
#[test]
fn test_conflict_resolution_all_variants() {
let variants = [
X86ConflictResolution::KeepFirst,
X86ConflictResolution::KeepLast,
X86ConflictResolution::Merge,
];
assert_eq!(variants.len(), 3);
}
#[test]
fn test_namespace_ref_type_all_variants() {
let types = [
X86NamespaceRefType::QualifiedUse,
X86NamespaceRefType::UnqualifiedUse,
X86NamespaceRefType::UsingDeclaration,
X86NamespaceRefType::UsingDirective,
X86NamespaceRefType::ForwardDeclaration,
X86NamespaceRefType::FriendDeclaration,
X86NamespaceRefType::TemplateSpecialization,
];
assert_eq!(types.len(), 7);
}
#[test]
fn test_include_sort_style_all_variants() {
let styles = [
X86IncludeSortStyle::LLVM,
X86IncludeSortStyle::Google,
X86IncludeSortStyle::Custom,
X86IncludeSortStyle::None,
];
assert_eq!(styles.len(), 4);
}
#[test]
fn test_include_language_all_variants() {
let langs = [
X86IncludeLanguage::C,
X86IncludeLanguage::Cpp,
X86IncludeLanguage::Both,
];
assert_eq!(langs.len(), 3);
}
#[test]
fn test_include_fix_result_all_fields() {
let result = X86IncludeFixResult {
file: PathBuf::from("test.c"),
missing_includes: vec!["stdio.h".into()],
unused_includes: vec!["unused.h".into()],
sorted: true,
patches_applied: 2,
};
assert_eq!(result.missing_includes.len(), 1);
assert_eq!(result.unused_includes.len(), 1);
assert!(result.sorted);
}
#[test]
fn test_doc_extract_javadoc_single_line() {
let source = "/** Single line doc */\nint x = 5;";
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source(source, Path::new("test.c"))
.unwrap();
assert!(!comments.is_empty());
}
#[test]
fn test_doc_extract_comment_with_leading_stars() {
let source = " /**\n * Line one\n * Line two\n */\nvoid func() {}";
let mut doc = X86ClangDoc::new();
let comments = doc
.extract_from_source(source, Path::new("test.c"))
.unwrap();
assert!(!comments.is_empty());
}
#[test]
fn test_reorder_fields_find_struct_names_forward_decl() {
let rf = X86ClangReorderFields::new();
let source = "struct Forward;\nstruct Defined { int x; };";
let names = rf.find_struct_names(source);
assert!(names.contains(&"Defined".to_string()));
}
#[test]
fn test_scan_deps_generate_compilation_entry_no_args() {
let sd = X86ScanDeps::new();
let entry = sd.generate_compilation_entry(Path::new("test.c"), Path::new("."), &[]);
assert_eq!(entry.file, "test.c");
assert!(entry.arguments.is_empty());
}
#[test]
fn test_diag_tool_analyze_file_empty() {
let dt = X86DiagTool::new();
let result = dt.analyze_file("/nonexistent/empty.c");
assert!(result.is_err());
}
#[test]
fn test_full_tooling_default_creates_subtools() {
let tools = X86ClangToolsFull::default();
assert!(!tools.apply_replacements.config.dry_run);
assert!(tools.change_namespace.update_qualifiers);
assert!(!tools.doc.verbose);
assert!(tools.include_fixer.enabled);
assert!(!tools.clang_move.dry_run);
assert!(!tools.rename.dry_run);
assert!(tools.reorder_fields.analyze_only);
assert!(tools.scan_deps.scan_modules);
assert!(!tools.diag_tool.verbose);
}
#[test]
fn test_doc_database_new() {
let db = X86DocDatabase::new();
assert!(db.entries.is_empty());
assert!(!db.modified);
assert!(db.db_path.is_none());
}
#[test]
fn test_doc_database_add_entry() {
let mut db = X86DocDatabase::new();
db.add_entry(X86DocComment {
symbol_name: "test_func".into(),
symbol_kind: X86DocSymbolKind::Function,
brief: "A test".into(),
full_text: String::new(),
params: Vec::new(),
return_desc: None,
exceptions: Vec::new(),
see_also: vec!["other".into()],
related: vec!["related_func".into()],
notes: Vec::new(),
warnings: Vec::new(),
preconditions: Vec::new(),
postconditions: Vec::new(),
deprecated: None,
since: None,
authors: Vec::new(),
file_path: PathBuf::from("test.c"),
line: 1,
});
assert_eq!(db.entries.len(), 1);
assert!(db.modified);
assert!(db.lookup("test_func").is_some());
}
#[test]
fn test_doc_database_lookup_and_search() {
let mut db = X86DocDatabase::new();
db.add_entry(X86DocComment {
symbol_name: "find_index".into(),
symbol_kind: X86DocSymbolKind::Function,
brief: "Finds an index".into(),
full_text: "Searches the array for an index".into(),
params: Vec::new(),
return_desc: None,
exceptions: Vec::new(),
see_also: vec!["binary_search".into()],
related: vec!["sort".into()],
notes: Vec::new(),
warnings: Vec::new(),
preconditions: Vec::new(),
postconditions: Vec::new(),
deprecated: None,
since: None,
authors: Vec::new(),
file_path: PathBuf::from("test.c"),
line: 1,
});
assert_eq!(db.by_kind(X86DocSymbolKind::Function).len(), 1);
assert_eq!(db.by_kind(X86DocSymbolKind::Class).len(), 0);
assert_eq!(db.search("index").len(), 1);
assert_eq!(db.referenced_by("binary_search").len(), 1);
assert!(db.lookup("nonexistent").is_none());
}
#[test]
fn test_doc_database_export_formats() {
let mut db = X86DocDatabase::new();
db.add_entry(X86DocComment {
symbol_name: "f".into(),
symbol_kind: X86DocSymbolKind::Function,
brief: "desc".into(),
full_text: String::new(),
params: Vec::new(),
return_desc: None,
exceptions: Vec::new(),
see_also: Vec::new(),
related: Vec::new(),
notes: Vec::new(),
warnings: Vec::new(),
preconditions: Vec::new(),
postconditions: Vec::new(),
deprecated: None,
since: None,
authors: Vec::new(),
file_path: PathBuf::from("test.c"),
line: 1,
});
assert!(db.export_yaml().contains("name: f"));
assert!(db.export_json().contains("\"name\""));
}
#[test]
fn test_include_map_builtins() {
let map = X86IncludeMap::new();
assert_eq!(map.lookup("open"), Some("fcntl.h"));
assert_eq!(map.lookup("close"), Some("unistd.h"));
assert_eq!(map.lookup("socket"), Some("sys/socket.h"));
assert_eq!(map.lookup("nonexistent"), None);
assert!(!map.transitive_headers("std::string").is_empty());
assert!(map.transitive_headers("nonexistent").is_empty());
}
#[test]
fn test_refactoring_batch_operations() {
let mut batch = X86RefactoringBatch::new();
assert!(batch.is_empty());
batch.add_rename(X86RenameOperation {
old_name: "a".into(),
new_name: "b".into(),
symbol_kind: X86DocSymbolKind::Variable,
file_paths: vec![],
is_macro: false,
handle_overloads: true,
handle_template_specs: true,
qualification: None,
});
batch.field_reorders.push("S".into());
assert_eq!(batch.total_operations(), 2);
assert!(!batch.is_empty());
}
#[test]
fn test_severity_map_workflow() {
let mut map = X86DiagnosticSeverityMap::new();
map.promote_warning_group("-Wunused");
map.warn_as_error = true;
let flag = X86DiagFlag {
name: "test".into(),
flag: "-Wtest".into(),
category: X86DiagCategory::Warning,
description: "t".into(),
default_severity: X86DiagSeverityLevel::Warning,
enabled_by_default: true,
groups: vec!["-Wunused".into()],
since_version: None,
};
assert_eq!(map.effective_severity(&flag), X86DiagSeverityLevel::Error);
map.clear();
assert!(map.mappings.is_empty());
}
#[test]
fn test_all_clone_implementations() {
let comment = X86DocComment {
symbol_name: "fn".into(),
symbol_kind: X86DocSymbolKind::Function,
brief: "b".into(),
full_text: "f".into(),
params: vec![X86DocParam {
name: "x".into(),
description: "d".into(),
direction: X86DocParamDirection::In,
}],
return_desc: Some("r".into()),
exceptions: vec![X86DocException {
exception_type: "e".into(),
description: "d".into(),
}],
see_also: vec!["s".into()],
related: vec!["r".into()],
notes: vec!["n".into()],
warnings: vec!["w".into()],
preconditions: vec!["p".into()],
postconditions: vec!["po".into()],
deprecated: Some("1.0".into()),
since: Some("0.5".into()),
authors: vec!["A".into()],
file_path: PathBuf::from("t.c"),
line: 42,
};
let _ = comment.clone();
let dep = X86ModuleDep {
module_name: "m".into(),
module_file: PathBuf::from("m.cppm"),
context_hash: "h".into(),
is_interface: true,
imported_modules: vec![],
};
let _ = dep.clone();
}
#[test]
fn test_replacement_edge_cases() {
assert!(X86Replacement::new("f.c", 0, 0, "x", "d").apply("").is_ok());
assert!(X86Replacement::new("f.c", 0, 10, "x", "d")
.apply("short")
.is_ok());
assert!(X86Replacement::new("f.c", 100, 5, "x", "d")
.apply("xxx")
.is_err());
}
#[test]
fn test_source_range_edge_cases() {
let r = X86SourceRange::new(1, 1, 5, 80);
assert!(r.contains(1, 1));
assert!(r.contains(5, 80));
assert!(!r.contains(0, 1));
assert!(!r.contains(6, 1));
assert!(!r.contains(1, 0));
assert!(!r.contains(5, 81));
}
#[test]
fn test_namespace_operations() {
let ns = X86NamespacePath::from_str("a::b::c::d");
assert_eq!(ns.components.len(), 4);
assert_eq!(ns.parent().unwrap().to_string(), "a::b::c");
assert_eq!(ns.leaf().unwrap().name, "d");
assert!(X86NamespacePath::from_str("single").parent().is_none());
}
#[test]
fn test_include_fixer_exhaustive() {
let fixer = X86ClangIncludeFixer::new();
assert!(fixer.include_mappings.len() > 10);
assert_eq!(
X86ClangIncludeFixer::extract_include_header("not include"),
None
);
let missing =
fixer.find_missing_includes("std::vector<int> v; std::sort(v.begin(), v.end());");
assert!(!missing.is_empty());
}
#[test]
fn test_reorder_fields_big_struct() {
let rf = X86ClangReorderFields::new();
let fields: Vec<X86FieldInfo> = (0..200)
.map(|i| X86FieldInfo {
name: format!("f{}", i),
field_type: "int".into(),
size_bytes: 4,
alignment_bytes: 4,
offset_bytes: 0,
is_bitfield: false,
bitfield_width: 0,
is_padding: false,
})
.collect();
let optimal = rf.compute_optimal_layout(&fields);
assert_eq!(optimal.len(), 200);
}
#[test]
fn test_scan_deps_defaults() {
let sd = X86ScanDeps::new();
assert_eq!(sd.target_triple, "x86_64-unknown-linux-gnu");
assert!(sd.include_paths.contains(&PathBuf::from(".")));
}
#[test]
fn test_diag_tool_flag_version() {
let mut dt = X86DiagTool::new();
dt.flags.push(X86DiagFlag {
name: "v".into(),
flag: "-Wv".into(),
category: X86DiagCategory::Warning,
description: "v".into(),
default_severity: X86DiagSeverityLevel::Warning,
enabled_by_default: true,
groups: vec![],
since_version: Some("15".into()),
});
let f = dt.get_flag_doc("-Wv").unwrap();
assert_eq!(f.since_version, Some("15".into()));
}
#[test]
fn test_yaml_complex_parse() {
let yaml = "---\nMainSourceFile: 'test.c'\nReplacements:\n - FilePath: 'a.c'\n Offset: 0\n Length: 1\n ReplacementText: 'x'\n - FilePath: 'b.c'\n Offset: 5\n Length: 2\n ReplacementText: 'yy'\n...\n";
let mut ar = X86ClangApplyReplacements::new();
assert_eq!(
ar.parse_yaml_replacements(yaml, Path::new("f.yaml"))
.unwrap(),
2
);
}
#[test]
fn test_json_empty_array() {
let mut ar = X86ClangApplyReplacements::new();
assert_eq!(
ar.parse_json_replacements("[]", Path::new("f.json"))
.unwrap(),
0
);
}
#[test]
fn test_replacement_apply_preserves_offsets() {
let source = "abcdefghij";
let repl = X86Replacement::new("f.c", 3, 4, "XY", "test");
let result = repl.apply(source).unwrap();
assert_eq!(result, "abcXYhij");
}
#[test]
fn test_replacement_apply_delete() {
let source = "hello world";
let repl = X86Replacement::new("f.c", 5, 6, "", "delete space+world");
let result = repl.apply(source).unwrap();
assert_eq!(result, "hello");
}
#[test]
fn test_replacement_apply_insert() {
let source = "hello";
let repl = X86Replacement::new("f.c", 5, 0, " world", "insert");
let result = repl.apply(source).unwrap();
assert_eq!(result, "hello world");
}
#[test]
fn test_apply_replacements_multiple_files_same_offset() {
let mut ar = X86ClangApplyReplacements::new();
ar.add_replacement(X86Replacement::new("a.c", 5, 3, "AAA", ""));
ar.add_replacement(X86Replacement::new("b.c", 5, 3, "BBB", ""));
assert_eq!(ar.total_replacements(), 2);
assert_eq!(ar.files_with_replacements().len(), 2);
}
#[test]
fn test_change_namespace_find_references_qualified() {
let mut cn = X86ClangChangeNamespace::new();
cn.source_cache.insert(
PathBuf::from("test.cpp"),
"ns::Foo f;\nns::Foo* p = &f;\n".to_string(),
);
let decl = X86NamespaceDeclaration {
name: "Foo".into(),
qualified_name: "ns::Foo".into(),
source_namespace: X86NamespacePath::from_str("ns"),
target_namespace: X86NamespacePath::from_str("newns"),
decl_type: X86NamespaceDeclType::Class,
file_path: PathBuf::from("test.cpp"),
range: X86SourceRange::new(1, 1, 1, 10),
};
let refs = cn.find_references(&decl);
assert!(refs.len() >= 2);
}
#[test]
fn test_doc_generate_rst_with_formatting() {
let mut doc = X86ClangDoc::new();
doc.project_name = "MyProject".into();
doc.project_version = "1.0".into();
doc.comments.push(X86DocComment {
symbol_name: "my_func".into(),
symbol_kind: X86DocSymbolKind::Function,
brief: "Does something".into(),
full_text: "Does something important".into(),
params: vec![X86DocParam {
name: "x".into(),
description: "the input".into(),
direction: X86DocParamDirection::In,
}],
return_desc: Some("the output".into()),
exceptions: vec![X86DocException {
exception_type: "std::runtime_error".into(),
description: "on failure".into(),
}],
see_also: vec!["other_func".into()],
related: vec!["helper".into()],
notes: vec!["This is important".into()],
warnings: vec!["Use with caution".into()],
preconditions: vec!["x > 0".into()],
postconditions: vec!["result >= 0".into()],
deprecated: Some("Use new_func instead".into()),
since: Some("2.0".into()),
authors: vec!["Jane Doe".into()],
file_path: PathBuf::from("lib.c"),
line: 100,
});
let rst = doc.generate_rst();
assert!(rst.contains("MyProject"));
assert!(rst.contains("my_func"));
assert!(rst.contains("Parameters"));
assert!(rst.contains("Returns"));
}
#[test]
fn test_reorder_fields_suboptimal_lp64() {
let mut rf = X86ClangReorderFields::new();
rf.target_abi = X86TargetABI::LP64;
let source = "struct S {\n char c1;\n double d;\n char c2;\n int i;\n};\n";
let analysis = rf.analyze_struct(source, "S");
assert!(analysis.bytes_saved > 0);
}
#[test]
fn test_classify_symbol_edge_cases() {
assert!(matches!(
X86ClangDoc::classify_symbol_from_line("enum class Color { Red }"),
X86DocSymbolKind::Enum
));
assert!(matches!(
X86ClangDoc::classify_symbol_from_line("namespace ns {"),
X86DocSymbolKind::Namespace
));
assert!(matches!(
X86ClangDoc::classify_symbol_from_line("typedef int MyInt;"),
X86DocSymbolKind::TypeAlias
));
}
#[test]
fn test_extract_symbol_name_complex() {
assert_eq!(
X86ClangDoc::extract_symbol_name("const unsigned long int foo;"),
"foo"
);
assert_eq!(
X86ClangDoc::extract_symbol_name(
"static inline __attribute__((always_inline)) int bar(void)"
),
"bar"
);
}
#[test]
fn test_parse_field_edge_cases() {
let rf = X86ClangReorderFields::new();
let field = rf.parse_field_line("unsigned int flags : 8;");
assert!(field.is_some());
let f = field.unwrap();
assert!(f.is_bitfield);
assert_eq!(f.bitfield_width, 8);
assert!(rf.parse_field_line("void doSomething(int x);").is_none());
assert!(rf.parse_field_line("public:").is_none());
assert!(rf.parse_field_line("private:").is_none());
assert!(rf.parse_field_line("protected:").is_none());
}
#[test]
fn test_scan_module_imports_complex() {
let sd = X86ScanDeps::new();
let source = "import std.core;\nimport std.io;\nexport import my.lib;\n";
let modules = sd.scan_module_imports(source);
assert_eq!(modules.len(), 3);
assert!(!modules[0].is_interface);
assert!(!modules[1].is_interface);
assert!(modules[2].is_interface);
}
#[test]
fn test_move_classify_symbols() {
let m = X86ClangMove::new();
let source = "struct Point { double x, y; };\nclass Widget { int id; };\n";
let r1 = m.find_definition_range(source, "Point");
let r2 = m.find_definition_range(source, "Widget");
assert!(r1.is_some());
assert!(r2.is_some());
}
#[test]
fn test_variant_count_assertions() {
assert_eq!(
[
X86NamespaceDeclType::Function,
X86NamespaceDeclType::Class,
X86NamespaceDeclType::Struct,
X86NamespaceDeclType::Enum,
X86NamespaceDeclType::Variable,
X86NamespaceDeclType::TypeAlias,
X86NamespaceDeclType::UsingDeclaration,
X86NamespaceDeclType::TemplateFunction,
X86NamespaceDeclType::TemplateClass,
X86NamespaceDeclType::EnumClass,
]
.len(),
10
);
assert_eq!(
[
X86SymbolRefType::Definition,
X86SymbolRefType::Declaration,
X86SymbolRefType::Call,
X86SymbolRefType::Reference,
X86SymbolRefType::TemplateSpecialization,
X86SymbolRefType::MacroExpansion,
X86SymbolRefType::IncludeDirective,
]
.len(),
7
);
assert_eq!(
[
X86NamespaceRefType::QualifiedUse,
X86NamespaceRefType::UnqualifiedUse,
X86NamespaceRefType::UsingDeclaration,
X86NamespaceRefType::UsingDirective,
X86NamespaceRefType::ForwardDeclaration,
X86NamespaceRefType::FriendDeclaration,
X86NamespaceRefType::TemplateSpecialization,
]
.len(),
7
);
}
#[test]
fn test_apply_replacements_parse_json_array_of_objects() {
let json = r#"[
{"FilePath": "a.c", "Offset": 0, "Length": 3, "ReplacementText": "AAA"},
{"FilePath": "b.c", "Offset": 5, "Length": 2, "ReplacementText": "BB"}
]"#;
let mut ar = X86ClangApplyReplacements::new();
let count = ar
.parse_json_replacements(json, Path::new("r.json"))
.unwrap();
assert_eq!(count, 2);
}
#[test]
fn test_large_number_of_replacements() {
let mut ar = X86ClangApplyReplacements::new();
for i in 0..1000 {
ar.add_replacement(X86Replacement::new(
format!("file_{}.c", i % 10),
i * 10,
5,
"X",
format!("repl_{}", i),
));
}
assert_eq!(ar.total_replacements(), 1000);
let conflicts = ar.detect_all_conflicts();
assert_eq!(conflicts, 0);
}
#[test]
fn test_many_doc_comments() {
let mut doc = X86ClangDoc::new();
for i in 0..500 {
doc.comments.push(X86DocComment {
symbol_name: format!("func_{}", i),
symbol_kind: X86DocSymbolKind::Function,
brief: format!("Function number {}", i),
full_text: String::new(),
params: Vec::new(),
return_desc: None,
exceptions: Vec::new(),
see_also: Vec::new(),
related: Vec::new(),
notes: Vec::new(),
warnings: Vec::new(),
preconditions: Vec::new(),
postconditions: Vec::new(),
deprecated: None,
since: None,
authors: Vec::new(),
file_path: PathBuf::from("test.c"),
line: i + 1,
});
}
assert_eq!(doc.comments.len(), 500);
let md = doc.generate_markdown();
assert!(md.contains("func_0"));
assert!(md.contains("func_499"));
}
#[test]
fn test_full_tooling_default_creates_subtools_v2() {
let tools = X86ClangToolsFull::default();
assert!(!tools.apply_replacements.config.dry_run);
assert!(tools.change_namespace.update_qualifiers);
assert!(!tools.doc.verbose);
assert!(tools.include_fixer.enabled);
assert!(!tools.clang_move.dry_run);
assert!(!tools.rename.dry_run);
assert!(tools.reorder_fields.analyze_only);
assert!(tools.scan_deps.scan_modules);
assert!(!tools.diag_tool.verbose);
}
#[test]
fn test_rename_with_operator_function() {
let r = X86ClangRename::new();
let source = "bool operator==(const T& a, const T& b) { return true; }";
let refs = r.find_references(source, "operator==", Path::new("test.cpp"));
let _ = refs.len();
}
#[test]
fn test_scan_deps_generate_compilation_entry_no_args_v2() {
let sd = X86ScanDeps::new();
let entry = sd.generate_compilation_entry(Path::new("test.c"), Path::new("."), &[]);
assert_eq!(entry.file, "test.c");
assert!(entry.arguments.is_empty());
}
#[test]
fn test_diag_tool_analyze_file_empty_v2() {
let dt = X86DiagTool::new();
let result = dt.analyze_file("/nonexistent/empty.c");
assert!(result.is_err());
}
#[test]
fn test_doc_output_format_coverage() {
let doc = X86ClangDoc::new();
for fmt in &[
X86DocOutputFormat::Markdown,
X86DocOutputFormat::HTML,
X86DocOutputFormat::ManPage,
X86DocOutputFormat::YAML,
X86DocOutputFormat::JSON,
X86DocOutputFormat::RST,
X86DocOutputFormat::PlainText,
] {
let output = doc.generate(*fmt);
let _ = output.len();
}
}
#[test]
fn test_include_fixer_sort_llvm_with_groups() {
let fixer = X86ClangIncludeFixer::new();
let lines = vec![
"#include \"local.h\"".to_string(),
"#include <vector>".to_string(),
"#include <algorithm>".to_string(),
"#include \"project/header.h\"".to_string(),
];
let sorted = fixer.sort_include_lines(&lines);
assert!(!sorted.is_empty());
}
#[test]
fn test_doc_database_default() {
let db = X86DocDatabase::default();
assert!(db.entries.is_empty());
}
#[test]
fn test_include_map_default() {
let map = X86IncludeMap::default();
assert!(!map.mappings.is_empty());
}
#[test]
fn test_severity_map_default() {
let map = X86DiagnosticSeverityMap::default();
assert!(!map.warn_as_error);
}
#[test]
fn test_refactoring_batch_default() {
let batch = X86RefactoringBatch::default();
assert!(batch.is_empty());
}
#[test]
fn test_diag_category_all_variants_match() {
for cat in &[
X86DiagCategory::Warning,
X86DiagCategory::Error,
X86DiagCategory::Note,
X86DiagCategory::Remark,
X86DiagCategory::Ignored,
] {
let s = cat.to_string();
assert!(!s.is_empty());
}
}
#[test]
fn test_diag_severity_all_levels() {
let levels = [
X86DiagSeverityLevel::Ignored,
X86DiagSeverityLevel::Note,
X86DiagSeverityLevel::Remark,
X86DiagSeverityLevel::Warning,
X86DiagSeverityLevel::Error,
X86DiagSeverityLevel::Fatal,
];
for lvl in &levels {
assert!(!lvl.to_string().is_empty());
}
}
#[test]
fn test_reorder_fields_long_double_all_abis() {
let mut rf_lp64 = X86ClangReorderFields::new();
rf_lp64.target_abi = X86TargetABI::LP64;
assert_eq!(rf_lp64.type_size_align("long double"), (16, 16));
let mut rf_ilp32 = X86ClangReorderFields::new();
rf_ilp32.target_abi = X86TargetABI::ILP32;
assert_eq!(rf_ilp32.type_size_align("long double"), (12, 4));
let mut rf_llp64 = X86ClangReorderFields::new();
rf_llp64.target_abi = X86TargetABI::LLP64;
assert_eq!(rf_llp64.type_size_align("long double"), (8, 8));
}
}