#[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 X86RefactorLocation {
pub line: usize,
pub column: usize,
}
impl X86RefactorLocation {
pub const fn new(line: usize, column: usize) -> Self {
Self { line, column }
}
pub const fn zero() -> Self {
Self { line: 0, column: 0 }
}
pub fn is_zero(&self) -> bool {
self.line == 0 && self.column == 0
}
}
impl fmt::Display for X86RefactorLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct X86RefactorRange {
pub start: X86RefactorLocation,
pub end: X86RefactorLocation,
}
impl X86RefactorRange {
pub const fn new(start: X86RefactorLocation, end: X86RefactorLocation) -> Self {
Self { start, end }
}
pub fn contains(&self, loc: X86RefactorLocation) -> bool {
if loc.line < self.start.line || loc.line > self.end.line {
return false;
}
if loc.line == self.start.line && loc.column < self.start.column {
return false;
}
if loc.line == self.end.line && loc.column > self.end.column {
return false;
}
true
}
pub fn overlaps(&self, other: &Self) -> bool {
if self.end.line < other.start.line {
return false;
}
if other.end.line < self.start.line {
return false;
}
if self.end.line == other.start.line && self.end.column < other.start.column {
return false;
}
if other.end.line == self.start.line && other.end.column < self.start.column {
return false;
}
true
}
pub fn is_empty(&self) -> bool {
self.start == self.end
}
}
impl fmt::Display for X86RefactorRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{} - {}]", self.start, self.end)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86SymbolKind {
Variable,
Function,
Class,
Namespace,
Macro,
Enum,
EnumConstant,
Typedef,
TypeAlias,
Template,
TemplateParameter,
Concept,
Method,
Field,
Parameter,
LocalVariable,
Label,
UsingDirective,
UsingDeclaration,
}
impl fmt::Display for X86SymbolKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Variable => write!(f, "variable"),
Self::Function => write!(f, "function"),
Self::Class => write!(f, "class"),
Self::Namespace => write!(f, "namespace"),
Self::Macro => write!(f, "macro"),
Self::Enum => write!(f, "enum"),
Self::EnumConstant => write!(f, "enum constant"),
Self::Typedef => write!(f, "typedef"),
Self::TypeAlias => write!(f, "type alias"),
Self::Template => write!(f, "template"),
Self::TemplateParameter => write!(f, "template parameter"),
Self::Concept => write!(f, "concept"),
Self::Method => write!(f, "method"),
Self::Field => write!(f, "field"),
Self::Parameter => write!(f, "parameter"),
Self::LocalVariable => write!(f, "local variable"),
Self::Label => write!(f, "label"),
Self::UsingDirective => write!(f, "using directive"),
Self::UsingDeclaration => write!(f, "using declaration"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86SymbolRef {
pub file: PathBuf,
pub range: X86RefactorRange,
pub kind: X86SymbolKind,
pub name: String,
pub context: String,
pub is_declaration: bool,
pub is_definition: bool,
pub qualified_name: Option<String>,
pub parent_scope: Option<String>,
pub in_macro: bool,
pub in_comment: bool,
pub in_string: bool,
}
impl X86SymbolRef {
pub fn new(file: PathBuf, range: X86RefactorRange, kind: X86SymbolKind, name: String) -> Self {
Self {
file,
range,
kind,
name,
context: String::new(),
is_declaration: false,
is_definition: false,
qualified_name: None,
parent_scope: None,
in_macro: false,
in_comment: false,
in_string: false,
}
}
pub fn with_context(mut self, ctx: impl Into<String>) -> Self {
self.context = ctx.into();
self
}
pub fn as_declaration(mut self, decl: bool) -> Self {
self.is_declaration = decl;
self
}
pub fn as_definition(mut self, def: bool) -> Self {
self.is_definition = def;
self
}
pub fn with_qualified_name(mut self, qn: impl Into<String>) -> Self {
self.qualified_name = Some(qn.into());
self
}
pub fn with_parent_scope(mut self, scope: impl Into<String>) -> Self {
self.parent_scope = Some(scope.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86RefactoringKind {
Rename,
ExtractFunction,
ExtractVariable,
InlineFunction,
InlineVariable,
MoveDefinition,
ChangeSignature,
ExtractInterface,
ExtractClass,
PullUp,
PushDown,
ReplaceType,
AddParameter,
RemoveParameter,
ReorderParameters,
ReplaceMacro,
ExpandMacro,
ExtractMethod,
ConvertToLambda,
ConvertFromLambda,
AddOverride,
SimplifyQualifications,
}
impl fmt::Display for X86RefactoringKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Rename => write!(f, "rename"),
Self::ExtractFunction => write!(f, "extract function"),
Self::ExtractVariable => write!(f, "extract variable"),
Self::InlineFunction => write!(f, "inline function"),
Self::InlineVariable => write!(f, "inline variable"),
Self::MoveDefinition => write!(f, "move definition"),
Self::ChangeSignature => write!(f, "change signature"),
Self::ExtractInterface => write!(f, "extract interface"),
Self::ExtractClass => write!(f, "extract class"),
Self::PullUp => write!(f, "pull up"),
Self::PushDown => write!(f, "push down"),
Self::ReplaceType => write!(f, "replace type"),
Self::AddParameter => write!(f, "add parameter"),
Self::RemoveParameter => write!(f, "remove parameter"),
Self::ReorderParameters => write!(f, "reorder parameters"),
Self::ReplaceMacro => write!(f, "replace macro"),
Self::ExpandMacro => write!(f, "expand macro"),
Self::ExtractMethod => write!(f, "extract method"),
Self::ConvertToLambda => write!(f, "convert to lambda"),
Self::ConvertFromLambda => write!(f, "convert from lambda"),
Self::AddOverride => write!(f, "add override"),
Self::SimplifyQualifications => write!(f, "simplify qualifications"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86ChangeType {
Replace,
Insert,
Delete,
CreateFile,
DeleteFile,
Move,
}
impl fmt::Display for X86ChangeType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Replace => write!(f, "replace"),
Self::Insert => write!(f, "insert"),
Self::Delete => write!(f, "delete"),
Self::CreateFile => write!(f, "create file"),
Self::DeleteFile => write!(f, "delete file"),
Self::Move => write!(f, "move"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86RefactoringChange {
pub kind: X86RefactoringKind,
pub change_type: X86ChangeType,
pub file: PathBuf,
pub range: X86RefactorRange,
pub new_text: String,
pub description: String,
pub original_text: Option<String>,
pub destination_file: Option<PathBuf>,
pub order_index: usize,
pub applied: bool,
pub reversible: bool,
}
impl X86RefactoringChange {
pub fn new(
kind: X86RefactoringKind,
change_type: X86ChangeType,
file: PathBuf,
range: X86RefactorRange,
new_text: String,
description: String,
) -> Self {
Self {
kind,
change_type,
file,
range,
new_text,
description,
original_text: None,
destination_file: None,
order_index: 0,
applied: false,
reversible: true,
}
}
pub fn with_original(mut self, text: impl Into<String>) -> Self {
self.original_text = Some(text.into());
self
}
pub fn with_destination(mut self, dest: PathBuf) -> Self {
self.destination_file = Some(dest);
self
}
pub fn with_order(mut self, idx: usize) -> Self {
self.order_index = idx;
self
}
pub fn as_irreversible(mut self) -> Self {
self.reversible = false;
self
}
}
impl fmt::Display for X86RefactoringChange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"[{}] {} {} in {:?}: {}",
self.kind, self.change_type, self.range, self.file, self.description
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86RefactorErrorSeverity {
Info,
Warning,
Error,
Fatal,
}
impl fmt::Display for X86RefactorErrorSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Info => write!(f, "info"),
Self::Warning => write!(f, "warning"),
Self::Error => write!(f, "error"),
Self::Fatal => write!(f, "fatal"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86RefactorError {
pub severity: X86RefactorErrorSeverity,
pub message: String,
pub file: Option<PathBuf>,
pub location: Option<X86RefactorLocation>,
pub code: Option<String>,
pub hint: Option<String>,
}
impl X86RefactorError {
pub fn new(severity: X86RefactorErrorSeverity, message: impl Into<String>) -> Self {
Self {
severity,
message: message.into(),
file: None,
location: None,
code: None,
hint: None,
}
}
pub fn error(message: impl Into<String>) -> Self {
Self::new(X86RefactorErrorSeverity::Error, message)
}
pub fn warning(message: impl Into<String>) -> Self {
Self::new(X86RefactorErrorSeverity::Warning, message)
}
pub fn info(message: impl Into<String>) -> Self {
Self::new(X86RefactorErrorSeverity::Info, message)
}
pub fn fatal(message: impl Into<String>) -> Self {
Self::new(X86RefactorErrorSeverity::Fatal, message)
}
pub fn at_file(mut self, file: PathBuf) -> Self {
self.file = Some(file);
self
}
pub fn at_location(mut self, loc: X86RefactorLocation) -> Self {
self.location = Some(loc);
self
}
pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
self.hint = Some(hint.into());
self
}
pub fn with_code(mut self, code: impl Into<String>) -> Self {
self.code = Some(code.into());
self
}
}
impl fmt::Display for X86RefactorError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.severity, self.message)?;
if let Some(ref file) = self.file {
write!(f, " [in {:?}]", file)?;
}
if let Some(ref loc) = self.location {
write!(f, " at {}", loc)?;
}
if let Some(ref hint) = self.hint {
write!(f, " (hint: {})", hint)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct X86RefactoringResult {
pub success: bool,
pub kind: X86RefactoringKind,
pub changes: Vec<X86RefactoringChange>,
pub diagnostics: Vec<X86RefactorError>,
pub files_modified: usize,
pub lines_affected: usize,
pub reversible: bool,
pub duration_ms: u64,
pub build_verified: bool,
pub test_verified: bool,
}
impl X86RefactoringResult {
pub fn success(kind: X86RefactoringKind, changes: Vec<X86RefactoringChange>) -> Self {
let files_set: HashSet<&PathBuf> = changes.iter().map(|c| &c.file).collect();
let lines: usize = changes
.iter()
.map(|c| {
if c.change_type == X86ChangeType::DeleteFile
|| c.change_type == X86ChangeType::CreateFile
{
0
} else {
c.range.end.line - c.range.start.line + 1
}
})
.sum();
let files_count = files_set.len();
Self {
success: true,
kind,
changes,
diagnostics: Vec::new(),
files_modified: files_count,
lines_affected: lines,
reversible: true,
duration_ms: 0,
build_verified: false,
test_verified: false,
}
}
pub fn failure(kind: X86RefactoringKind, diagnostics: Vec<X86RefactorError>) -> Self {
Self {
success: false,
kind,
changes: Vec::new(),
diagnostics,
files_modified: 0,
lines_affected: 0,
reversible: false,
duration_ms: 0,
build_verified: false,
test_verified: false,
}
}
pub fn with_duration(mut self, ms: u64) -> Self {
self.duration_ms = ms;
self
}
pub fn with_build_verified(mut self, verified: bool) -> Self {
self.build_verified = verified;
self
}
pub fn with_test_verified(mut self, verified: bool) -> Self {
self.test_verified = verified;
self
}
pub fn errors(&self) -> Vec<&X86RefactorError> {
self.diagnostics
.iter()
.filter(|d| {
d.severity == X86RefactorErrorSeverity::Error
|| d.severity == X86RefactorErrorSeverity::Fatal
})
.collect()
}
pub fn warnings(&self) -> Vec<&X86RefactorError> {
self.diagnostics
.iter()
.filter(|d| d.severity == X86RefactorErrorSeverity::Warning)
.collect()
}
}
#[derive(Debug, Clone)]
pub struct X86SourceFile {
pub path: PathBuf,
pub lines: Vec<String>,
pub content: String,
pub dirty: bool,
pub language: X86SourceLanguage,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86SourceLanguage {
C,
Cpp,
Header,
ObjectiveC,
ObjectiveCpp,
Unknown,
}
impl fmt::Display for X86SourceLanguage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::C => write!(f, "C"),
Self::Cpp => write!(f, "C++"),
Self::Header => write!(f, "Header"),
Self::ObjectiveC => write!(f, "Objective-C"),
Self::ObjectiveCpp => write!(f, "Objective-C++"),
Self::Unknown => write!(f, "Unknown"),
}
}
}
impl X86SourceFile {
pub fn load(path: &Path) -> Result<Self, X86RefactorError> {
let content = fs::read_to_string(path).map_err(|e| {
X86RefactorError::fatal(format!("Failed to read file {:?}: {}", path, e))
.at_file(path.to_path_buf())
})?;
let lines: Vec<String> = content.lines().map(|l| l.to_string()).collect();
let language = Self::detect_language(path);
Ok(Self {
path: path.to_path_buf(),
lines,
content,
dirty: false,
language,
})
}
pub fn from_string(path: PathBuf, content: String) -> Self {
let language = Self::detect_language(&path);
let lines: Vec<String> = content.lines().map(|l| l.to_string()).collect();
Self {
path,
lines,
content,
dirty: false,
language,
}
}
fn detect_language(path: &Path) -> X86SourceLanguage {
match path.extension().and_then(|e| e.to_str()) {
Some("c") => X86SourceLanguage::C,
Some("cc") | Some("cpp") | Some("cxx") | Some("c++") | Some("C") => {
X86SourceLanguage::Cpp
}
Some("h") | Some("hh") | Some("hpp") | Some("hxx") | Some("H") => {
X86SourceLanguage::Header
}
Some("m") => X86SourceLanguage::ObjectiveC,
Some("mm") => X86SourceLanguage::ObjectiveCpp,
_ => X86SourceLanguage::Unknown,
}
}
pub fn text_in_range(&self, range: &X86RefactorRange) -> Option<String> {
if range.start.line == 0
|| range.end.line == 0
|| range.start.line > self.lines.len()
|| range.end.line > self.lines.len()
{
return None;
}
if range.start.line == range.end.line {
let line = &self.lines[range.start.line - 1];
if range.start.column == 0 || range.end.column == 0 {
return None;
}
let start = range.start.column.min(line.len() + 1) - 1;
let end = range.end.column.min(line.len() + 1) - 1;
if start > line.len() || end > line.len() || start > end {
return None;
}
Some(line[start..end].to_string())
} else {
let mut result = String::new();
for i in range.start.line..=range.end.line {
if i > self.lines.len() {
break;
}
let line = &self.lines[i - 1];
if i == range.start.line {
let start = (range.start.column - 1).min(line.len());
result.push_str(&line[start..]);
} else if i == range.end.line {
let end = (range.end.column - 1).min(line.len());
result.push_str(&line[..end]);
} else {
result.push_str(line);
}
if i < range.end.line {
result.push('\n');
}
}
Some(result)
}
}
pub fn line(&self, n: usize) -> Option<&str> {
if n == 0 || n > self.lines.len() {
return None;
}
Some(&self.lines[n - 1])
}
pub fn line_count(&self) -> usize {
self.lines.len()
}
pub fn rebuild_content(&self) -> String {
self.lines.join("\n")
}
}
#[derive(Debug, Clone)]
pub struct X86ParameterDesc {
pub name: String,
pub type_name: String,
pub default_value: String,
pub position: usize,
pub has_default: bool,
pub is_variadic: bool,
pub is_const: bool,
pub is_reference: bool,
pub is_rvalue_ref: bool,
pub attributes: Vec<String>,
}
impl X86ParameterDesc {
pub fn new(name: impl Into<String>, type_name: impl Into<String>, position: usize) -> Self {
Self {
name: name.into(),
type_name: type_name.into(),
default_value: String::new(),
position,
has_default: false,
is_variadic: false,
is_const: false,
is_reference: false,
is_rvalue_ref: false,
attributes: Vec::new(),
}
}
pub fn with_default(mut self, default: impl Into<String>) -> Self {
self.default_value = default.into();
self.has_default = true;
self
}
pub fn as_const(mut self) -> Self {
self.is_const = true;
self
}
pub fn as_reference(mut self) -> Self {
self.is_reference = true;
self
}
pub fn as_rvalue_ref(mut self) -> Self {
self.is_rvalue_ref = true;
self
}
pub fn as_variadic(mut self) -> Self {
self.is_variadic = true;
self
}
pub fn with_attributes(mut self, attrs: Vec<String>) -> Self {
self.attributes = attrs;
self
}
pub fn to_source(&self) -> String {
let mut s = String::new();
for attr in &self.attributes {
s.push_str(&format!("{} ", attr));
}
if self.is_const {
s.push_str("const ");
}
s.push_str(&self.type_name);
if self.is_rvalue_ref {
s.push_str("&& ");
} else if self.is_reference {
s.push_str("& ");
} else {
s.push(' ');
}
if self.is_variadic {
s.push_str("...");
} else {
s.push_str(&self.name);
}
if self.has_default {
s.push_str(&format!(" = {}", self.default_value));
}
s
}
pub fn to_decl(&self) -> String {
let mut s = String::new();
if self.is_const {
s.push_str("const ");
}
s.push_str(&self.type_name);
if self.is_rvalue_ref {
s.push_str("&& ");
} else if self.is_reference {
s.push_str("& ");
} else {
s.push(' ');
}
if self.is_variadic {
s.push_str("...");
} else {
s.push_str(&self.name);
}
s
}
}
#[derive(Debug, Clone)]
pub struct X86FunctionDesc {
pub name: String,
pub return_type: String,
pub parameters: Vec<X86ParameterDesc>,
pub is_method: bool,
pub class_name: Option<String>,
pub is_const_method: bool,
pub is_static: bool,
pub is_virtual: bool,
pub is_template: bool,
pub template_params: Vec<String>,
pub definition_file: PathBuf,
pub declaration_file: Option<PathBuf>,
pub body_range: Option<X86RefactorRange>,
pub definition_range: Option<X86RefactorRange>,
pub declaration_range: Option<X86RefactorRange>,
pub is_noexcept: bool,
pub is_override: bool,
pub is_final: bool,
pub return_type_is_auto: bool,
pub trailing_return_type: Option<String>,
pub attributes: Vec<String>,
}
impl X86FunctionDesc {
pub fn new(
name: impl Into<String>,
return_type: impl Into<String>,
definition_file: PathBuf,
) -> Self {
Self {
name: name.into(),
return_type: return_type.into(),
parameters: Vec::new(),
is_method: false,
class_name: None,
is_const_method: false,
is_static: false,
is_virtual: false,
is_template: false,
template_params: Vec::new(),
definition_file,
declaration_file: None,
body_range: None,
definition_range: None,
declaration_range: None,
is_noexcept: false,
is_override: false,
is_final: false,
return_type_is_auto: false,
trailing_return_type: None,
attributes: Vec::new(),
}
}
pub fn parameter_names(&self) -> Vec<&str> {
self.parameters.iter().map(|p| p.name.as_str()).collect()
}
pub fn parameter_count(&self) -> usize {
self.parameters.len()
}
pub fn to_signature(&self) -> String {
let mut s = String::new();
for attr in &self.attributes {
s.push_str(&format!("{} ", attr));
}
if self.is_template {
s.push_str("template<");
s.push_str(&self.template_params.join(", "));
s.push_str("> ");
}
if self.is_static {
s.push_str("static ");
}
if self.is_virtual {
s.push_str("virtual ");
}
if self.return_type_is_auto {
s.push_str("auto ");
} else {
s.push_str(&format!("{} ", self.return_type));
}
if let Some(ref class) = self.class_name {
s.push_str(&format!("{}::", class));
}
s.push_str(&self.name);
s.push('(');
let param_strs: Vec<String> = self.parameters.iter().map(|p| p.to_source()).collect();
s.push_str(¶m_strs.join(", "));
s.push(')');
if self.is_const_method {
s.push_str(" const");
}
if self.is_noexcept {
s.push_str(" noexcept");
}
if self.is_override {
s.push_str(" override");
}
if self.is_final {
s.push_str(" final");
}
if let Some(ref trailing) = self.trailing_return_type {
s.push_str(&format!(" -> {}", trailing));
}
s
}
pub fn add_param(&mut self, param: X86ParameterDesc) {
self.parameters.push(param);
}
pub fn remove_param(&mut self, position: usize) -> Option<X86ParameterDesc> {
if position < self.parameters.len() {
let removed = self.parameters.remove(position);
for (i, p) in self.parameters.iter_mut().enumerate() {
p.position = i;
}
Some(removed)
} else {
None
}
}
pub fn reorder_params(&mut self, new_order: &[usize]) -> Result<(), X86RefactorError> {
if new_order.len() != self.parameters.len() {
return Err(X86RefactorError::error(format!(
"Permutation length {} does not match parameter count {}",
new_order.len(),
self.parameters.len()
)));
}
let mut seen: HashSet<usize> = HashSet::new();
for &i in new_order {
if i >= self.parameters.len() {
return Err(X86RefactorError::error(format!(
"Invalid permutation index {} for {} parameters",
i,
self.parameters.len()
)));
}
if !seen.insert(i) {
return Err(X86RefactorError::error(format!(
"Duplicate index {} in permutation",
i
)));
}
}
let old_params = std::mem::take(&mut self.parameters);
self.parameters = new_order.iter().map(|&i| old_params[i].clone()).collect();
for (i, p) in self.parameters.iter_mut().enumerate() {
p.position = i;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct X86ClassDesc {
pub name: String,
pub is_struct: bool,
pub bases: Vec<X86BaseClassDesc>,
pub fields: Vec<X86MemberDesc>,
pub methods: Vec<X86FunctionDesc>,
pub nested_types: Vec<String>,
pub definition_file: PathBuf,
pub body_range: Option<X86RefactorRange>,
pub is_template: bool,
pub template_params: Vec<String>,
pub current_access: X86AccessSpecifier,
pub is_forward_decl: bool,
pub is_final: bool,
pub attributes: Vec<String>,
pub friends: Vec<String>,
pub using_declarations: Vec<String>,
pub typedefs: Vec<String>,
pub static_asserts: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86AccessSpecifier {
Public,
Protected,
Private,
Unspecified,
}
impl fmt::Display for X86AccessSpecifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Public => write!(f, "public"),
Self::Protected => write!(f, "protected"),
Self::Private => write!(f, "private"),
Self::Unspecified => write!(f, ""),
}
}
}
#[derive(Debug, Clone)]
pub struct X86BaseClassDesc {
pub class_name: String,
pub access: X86AccessSpecifier,
pub is_virtual: bool,
}
impl X86BaseClassDesc {
pub fn new(class_name: impl Into<String>, access: X86AccessSpecifier) -> Self {
Self {
class_name: class_name.into(),
access,
is_virtual: false,
}
}
}
#[derive(Debug, Clone)]
pub struct X86MemberDesc {
pub name: String,
pub type_name: String,
pub access: X86AccessSpecifier,
pub is_static: bool,
pub is_mutable: bool,
pub is_const: bool,
pub default_value: Option<String>,
pub source_range: Option<X86RefactorRange>,
pub attributes: Vec<String>,
pub is_bitfield: bool,
pub bitfield_width: Option<usize>,
}
impl X86MemberDesc {
pub fn new(name: impl Into<String>, type_name: impl Into<String>) -> Self {
Self {
name: name.into(),
type_name: type_name.into(),
access: X86AccessSpecifier::Unspecified,
is_static: false,
is_mutable: false,
is_const: false,
default_value: None,
source_range: None,
attributes: Vec::new(),
is_bitfield: false,
bitfield_width: None,
}
}
pub fn to_declaration(&self) -> String {
let mut s = String::new();
for attr in &self.attributes {
s.push_str(&format!("{} ", attr));
}
if self.is_static {
s.push_str("static ");
}
if self.is_mutable {
s.push_str("mutable ");
}
if self.is_const {
s.push_str("const ");
}
s.push_str(&self.type_name);
s.push(' ');
s.push_str(&self.name);
if self.is_bitfield {
if let Some(w) = self.bitfield_width {
s.push_str(&format!(" : {}", w));
}
}
if let Some(ref default) = self.default_value {
if !self.is_bitfield {
s.push_str(&format!(" = {}", default));
}
}
s.push(';');
s
}
}
impl X86ClassDesc {
pub fn new(name: impl Into<String>, definition_file: PathBuf) -> Self {
Self {
name: name.into(),
is_struct: false,
bases: Vec::new(),
fields: Vec::new(),
methods: Vec::new(),
nested_types: Vec::new(),
definition_file,
body_range: None,
is_template: false,
template_params: Vec::new(),
current_access: X86AccessSpecifier::Unspecified,
is_forward_decl: false,
is_final: false,
attributes: Vec::new(),
friends: Vec::new(),
using_declarations: Vec::new(),
typedefs: Vec::new(),
static_asserts: Vec::new(),
}
}
pub fn as_struct(mut self) -> Self {
self.is_struct = true;
self
}
pub fn as_class(mut self) -> Self {
self.is_struct = false;
self
}
pub fn field_names(&self) -> Vec<&str> {
self.fields.iter().map(|f| f.name.as_str()).collect()
}
pub fn method_names(&self) -> Vec<&str> {
self.methods.iter().map(|m| m.name.as_str()).collect()
}
pub fn all_member_names(&self) -> Vec<&str> {
let mut names: Vec<&str> = self.fields.iter().map(|f| f.name.as_str()).collect();
names.extend(self.methods.iter().map(|m| m.name.as_str()));
names
}
}
#[derive(Debug, Clone)]
pub struct X86Refactor {
pub files: HashMap<PathBuf, X86SourceFile>,
pub symbol_index: HashMap<String, Vec<X86SymbolRef>>,
pub changes: Vec<X86RefactoringChange>,
pub diagnostics: Vec<X86RefactorError>,
pub project_root: PathBuf,
pub dry_run: bool,
pub verbose: bool,
pub change_limit: usize,
pub auto_resolve: bool,
pub cross_file: bool,
pub source_extensions: Vec<String>,
pub header_extensions: Vec<String>,
pub excluded_paths: Vec<PathBuf>,
pub operation_history: Vec<X86RefactoringResult>,
}
impl X86Refactor {
pub fn new(project_root: PathBuf) -> Self {
Self {
files: HashMap::new(),
symbol_index: HashMap::new(),
changes: Vec::new(),
diagnostics: Vec::new(),
project_root,
dry_run: false,
verbose: false,
change_limit: 1000,
auto_resolve: true,
cross_file: true,
source_extensions: vec![
"c".into(),
"cc".into(),
"cpp".into(),
"cxx".into(),
"c++".into(),
"C".into(),
"m".into(),
"mm".into(),
],
header_extensions: vec![
"h".into(),
"hh".into(),
"hpp".into(),
"hxx".into(),
"H".into(),
"h++".into(),
],
excluded_paths: Vec::new(),
operation_history: Vec::new(),
}
}
pub fn load_file(&mut self, path: &Path) -> Result<&X86SourceFile, X86RefactorError> {
if !self.files.contains_key(path) {
let canonical = if path.is_absolute() {
path.to_path_buf()
} else {
self.project_root.join(path)
};
let source_file = X86SourceFile::load(&canonical)?;
self.files.insert(canonical.clone(), source_file);
}
Ok(&self.files[path])
}
pub fn load_file_from_string(&mut self, path: PathBuf, content: String) -> &X86SourceFile {
let source_file = X86SourceFile::from_string(path.clone(), content);
self.files.insert(path.clone(), source_file);
self.files.get(&path).unwrap()
}
pub fn get_file(&self, path: &Path) -> Option<&X86SourceFile> {
self.files.get(path)
}
pub fn get_file_mut(&mut self, path: &Path) -> Option<&mut X86SourceFile> {
self.files.get_mut(path)
}
pub fn load_directory(&mut self, dir: &Path) -> Result<usize, X86RefactorError> {
let mut count = 0;
let canonical = if dir.is_absolute() {
dir.to_path_buf()
} else {
self.project_root.join(dir)
};
self._load_directory_recursive(&canonical, &mut count)?;
Ok(count)
}
fn _load_directory_recursive(
&mut self,
dir: &Path,
count: &mut usize,
) -> Result<(), X86RefactorError> {
if self.excluded_paths.iter().any(|p| p == dir) {
return Ok(());
}
let entries = fs::read_dir(dir)
.map_err(|e| X86RefactorError::fatal(format!("Failed to read dir {:?}: {}", dir, e)))?;
for entry in entries {
let entry =
entry.map_err(|e| X86RefactorError::fatal(format!("Dir entry error: {}", e)))?;
let path = entry.path();
if path.is_dir() {
self._load_directory_recursive(&path, count)?;
} else if path.is_file() {
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
let is_source = self.source_extensions.iter().any(|se| se == ext);
let is_header = self.header_extensions.iter().any(|he| he == ext);
if is_source || is_header {
self.load_file(&path)?;
*count += 1;
}
}
}
}
Ok(())
}
pub fn index_symbols(&mut self) {
self.symbol_index.clear();
let files: Vec<_> = self.files.values().cloned().collect();
for file in &files {
self._index_file_symbols(file);
}
}
fn _index_file_symbols(&mut self, file: &X86SourceFile) {
let mut line_num = 0;
for line in &file.lines {
line_num += 1;
let mut col = 0;
let bytes = line.as_bytes();
while col < bytes.len() {
if !Self::_is_ident_start(bytes[col]) {
col += 1;
continue;
}
let start = col;
while col < bytes.len() && Self::_is_ident_continue(bytes[col]) {
col += 1;
}
let ident = &line[start..col];
if Self::_is_keyword(ident) {
continue;
}
let loc = X86RefactorLocation::new(line_num, start + 1);
let range = X86RefactorRange::new(loc, X86RefactorLocation::new(line_num, col + 1));
let sym_ref = X86SymbolRef::new(
file.path.clone(),
range,
X86SymbolKind::Variable, ident.to_string(),
)
.with_context(line.clone());
self.symbol_index
.entry(ident.to_string())
.or_insert_with(Vec::new)
.push(sym_ref);
}
}
}
fn _is_ident_start(b: u8) -> bool {
(b >= b'a' && b <= b'z') || (b >= b'A' && b <= b'Z') || b == b'_'
}
fn _is_ident_continue(b: u8) -> bool {
(b >= b'a' && b <= b'z')
|| (b >= b'A' && b <= b'Z')
|| (b >= b'0' && b <= b'9')
|| b == b'_'
}
fn _is_keyword(ident: &str) -> bool {
matches!(
ident,
"if" | "else"
| "for"
| "while"
| "do"
| "switch"
| "case"
| "default"
| "break"
| "continue"
| "return"
| "goto"
| "int"
| "char"
| "float"
| "double"
| "void"
| "long"
| "short"
| "signed"
| "unsigned"
| "const"
| "volatile"
| "static"
| "extern"
| "auto"
| "register"
| "struct"
| "union"
| "enum"
| "typedef"
| "sizeof"
| "typeof"
| "class"
| "namespace"
| "template"
| "typename"
| "public"
| "protected"
| "private"
| "virtual"
| "override"
| "final"
| "using"
| "new"
| "delete"
| "this"
| "nullptr"
| "true"
| "false"
| "noexcept"
| "constexpr"
| "consteval"
| "constinit"
| "thread_local"
| "inline"
| "explicit"
| "friend"
| "mutable"
| "operator"
| "try"
| "catch"
| "throw"
| "decltype"
| "alignas"
| "alignof"
| "static_assert"
| "asm"
| "and"
| "or"
| "not"
| "xor"
| "bitand"
| "bitor"
| "compl"
| "and_eq"
| "or_eq"
| "xor_eq"
| "not_eq"
)
}
pub fn find_references(&self, name: &str) -> Vec<&X86SymbolRef> {
self.symbol_index
.get(name)
.map(|refs| refs.iter().collect())
.unwrap_or_default()
}
pub fn find_references_in_file(&self, name: &str, file: &Path) -> Vec<&X86SymbolRef> {
self.symbol_index
.get(name)
.map(|refs| refs.iter().filter(|r| r.file == file).collect())
.unwrap_or_default()
}
pub fn record_change(&mut self, change: X86RefactoringChange) {
if self.verbose {
log::info!("Recording change: {}", change);
}
self.changes.push(change);
}
pub fn apply_changes(&mut self) -> Result<(), X86RefactorError> {
if self.dry_run {
return Ok(());
}
let mut changes_by_file: HashMap<PathBuf, Vec<&X86RefactoringChange>> = HashMap::new();
for change in &self.changes {
changes_by_file
.entry(change.file.clone())
.or_insert_with(Vec::new)
.push(change);
}
for (file_path, file_changes) in &mut changes_by_file {
file_changes.sort_by(|a, b| {
b.range
.start
.line
.cmp(&a.range.start.line)
.then(b.range.start.column.cmp(&a.range.start.column))
});
let mut lines = if let Some(file) = self.files.get(file_path) {
file.lines.clone()
} else {
return Err(X86RefactorError::error(format!(
"File {:?} not loaded for applying changes",
file_path
)));
};
for change in file_changes {
match change.change_type {
X86ChangeType::Replace | X86ChangeType::Insert | X86ChangeType::Delete => {
Self::_apply_text_change(&mut lines, change)?;
}
X86ChangeType::CreateFile => {
self._apply_create_file(change)?;
}
X86ChangeType::DeleteFile => {
self._apply_delete_file(change)?;
}
X86ChangeType::Move => {
self._apply_move(change)?;
}
}
}
if let Some(file) = self.files.get_mut(file_path) {
file.lines = lines;
file.content = file.rebuild_content();
file.dirty = true;
}
}
Ok(())
}
fn _apply_text_change(
lines: &mut Vec<String>,
change: &X86RefactoringChange,
) -> Result<(), X86RefactorError> {
match change.change_type {
X86ChangeType::Replace => {
if change.range.start.line == 0
|| change.range.end.line == 0
|| change.range.start.line > lines.len()
|| change.range.end.line > lines.len()
{
return Err(X86RefactorError::error(format!(
"Invalid range {} for replacement in file",
change.range
)));
}
if change.range.start.line == change.range.end.line {
let line_idx = change.range.start.line - 1;
let line = &lines[line_idx];
let start = change.range.start.column - 1;
let end = change.range.end.column - 1;
if start > line.len() || end > line.len() || start > end {
return Err(X86RefactorError::error(format!(
"Invalid column range {}-{} in line {} of length {}",
start,
end,
change.range.start.line,
line.len()
)));
}
let new_line = format!("{}{}{}", &line[..start], change.new_text, &line[end..]);
lines[line_idx] = new_line;
} else {
let start_idx = change.range.start.line - 1;
let end_idx = change.range.end.line - 1;
let first_line = &lines[start_idx];
let last_line = &lines[end_idx];
let start = change.range.start.column - 1;
let end = change.range.end.column - 1;
let prefix = &first_line[..start.min(first_line.len())];
let suffix = &last_line[end.min(last_line.len())..];
let new_first = format!("{}{}", prefix, change.new_text);
let new_lines: Vec<String> = new_first.lines().map(|l| l.to_string()).collect();
let suffix_added = if new_lines.is_empty() {
vec![suffix.to_string()]
} else {
let mut nl = new_lines;
let last = nl.last_mut().unwrap();
last.push_str(suffix);
nl
};
lines.drain(start_idx..=end_idx);
for (i, new_line) in suffix_added.iter().enumerate() {
lines.insert(start_idx + i, new_line.clone());
}
}
}
X86ChangeType::Insert => {
let line_idx = change.range.start.line - 1;
if line_idx > lines.len() {
return Err(X86RefactorError::error(
"Insert beyond file end".to_string(),
));
}
if line_idx == lines.len() {
for new_line in change.new_text.lines() {
lines.push(new_line.to_string());
}
} else {
let col = change.range.start.column - 1;
let line = &lines[line_idx];
let prefix = line[..col.min(line.len())].to_string();
let suffix = line[col.min(line.len())..].to_string();
let new_text_lines: Vec<&str> = change.new_text.lines().collect();
if new_text_lines.is_empty() {
lines[line_idx] = format!("{}{}", prefix, suffix);
} else if new_text_lines.len() == 1 {
lines[line_idx] = format!("{}{}{}", prefix, new_text_lines[0], suffix);
} else {
lines[line_idx] = format!("{}{}", prefix, new_text_lines[0]);
for i in 1..new_text_lines.len() - 1 {
lines.insert(line_idx + i, new_text_lines[i].to_string());
}
let last = new_text_lines[new_text_lines.len() - 1];
lines.insert(
line_idx + new_text_lines.len() - 1,
format!("{}{}", last, suffix),
);
}
}
}
X86ChangeType::Delete => {
if change.range.start.line == change.range.end.line {
let line_idx = change.range.start.line - 1;
let line = &lines[line_idx];
let start = change.range.start.column - 1;
let end = change.range.end.column - 1;
if start > line.len() || end > line.len() {
return Err(X86RefactorError::error(
"Invalid column for delete".to_string(),
));
}
lines[line_idx] = format!("{}{}", &line[..start], &line[end..]);
} else {
let start_idx = change.range.start.line - 1;
let end_idx = change.range.end.line - 1;
let first_line = &lines[start_idx];
let last_line = &lines[end_idx];
let start = change.range.start.column - 1;
let end = change.range.end.column - 1;
let prefix = &first_line[..start.min(first_line.len())];
let suffix = &last_line[end.min(last_line.len())..];
lines[start_idx] = format!("{}{}", prefix, suffix);
if end_idx > start_idx {
lines.drain(start_idx + 1..=end_idx);
}
}
}
_ => {}
}
Ok(())
}
fn _apply_create_file(&self, change: &X86RefactoringChange) -> Result<(), X86RefactorError> {
let parent = change.file.parent().ok_or_else(|| {
X86RefactorError::error(format!("No parent dir for {:?}", change.file))
})?;
fs::create_dir_all(parent).map_err(|e| {
X86RefactorError::fatal(format!("Failed to create dir {:?}: {}", parent, e))
})?;
fs::write(&change.file, &change.new_text).map_err(|e| {
X86RefactorError::fatal(format!("Failed to write {:?}: {}", change.file, e))
})?;
Ok(())
}
fn _apply_delete_file(&self, change: &X86RefactoringChange) -> Result<(), X86RefactorError> {
if change.file.exists() {
fs::remove_file(&change.file).map_err(|e| {
X86RefactorError::fatal(format!("Failed to delete {:?}: {}", change.file, e))
})?;
}
Ok(())
}
fn _apply_move(&self, change: &X86RefactoringChange) -> Result<(), X86RefactorError> {
let dest = change
.destination_file
.as_ref()
.ok_or_else(|| X86RefactorError::error("Move change has no destination".to_string()))?;
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent).map_err(|e| {
X86RefactorError::fatal(format!("Failed to create dir {:?}: {}", parent, e))
})?;
}
if change.new_text.is_empty() {
fs::rename(&change.file, dest).map_err(|e| {
X86RefactorError::fatal(format!(
"Failed to move {:?} -> {:?}: {}",
change.file, dest, e
))
})?;
} else {
fs::write(dest, &change.new_text).map_err(|e| {
X86RefactorError::fatal(format!("Failed to write dest {:?}: {}", dest, e))
})?;
}
Ok(())
}
pub fn flush_files(&mut self) -> Result<usize, X86RefactorError> {
let mut count = 0;
let dirty_files: Vec<PathBuf> = self
.files
.iter()
.filter(|(_, f)| f.dirty)
.map(|(p, _)| p.clone())
.collect();
for path in &dirty_files {
if let Some(file) = self.files.get(path) {
fs::write(path, &file.content).map_err(|e| {
X86RefactorError::fatal(format!("Failed to write {:?}: {}", path, e))
})?;
count += 1;
}
}
for path in &dirty_files {
if let Some(file) = self.files.get_mut(path) {
file.dirty = false;
}
}
Ok(count)
}
pub fn clear_changes(&mut self) {
self.changes.clear();
}
pub fn clear_diagnostics(&mut self) {
self.diagnostics.clear();
}
pub fn add_diagnostic(&mut self, diag: X86RefactorError) {
self.diagnostics.push(diag);
}
pub fn rename(
&mut self,
old_name: &str,
new_name: &str,
kind_filter: Option<X86SymbolKind>,
file_filter: Option<&Path>,
scope: Option<&str>,
) -> X86RefactoringResult {
if old_name.is_empty() || new_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(
"Old name and new name must not be empty",
)],
);
}
if old_name == new_name {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(
"New name must differ from old name",
)],
);
}
if !Self::_is_valid_identifier(new_name) {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(format!(
"'{}' is not a valid identifier",
new_name
))],
);
}
if self.symbol_index.is_empty() {
self.index_symbols();
}
let refs = self.find_references(old_name);
if refs.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![
X86RefactorError::warning(format!("No references found for '{}'", old_name))
.with_hint(
"Ensure the code has been indexed and the symbol name is correct",
),
],
);
}
let filtered_refs: Vec<&&X86SymbolRef> = refs
.iter()
.filter(|r| {
if let Some(kf) = &kind_filter {
if r.kind != *kf {
return false;
}
}
if let Some(ff) = file_filter {
if r.file != ff {
return false;
}
}
if let Some(s) = scope {
if let Some(ref ps) = r.parent_scope {
if ps != s {
return false;
}
} else {
return false;
}
}
if r.in_comment || r.in_string {
return false;
}
true
})
.collect();
if filtered_refs.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::warning(format!(
"No references match filters for '{}'",
old_name
))],
);
}
if filtered_refs.len() > self.change_limit {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(format!(
"Too many references ({}); increase change_limit or narrow filters",
filtered_refs.len()
))
.with_hint(format!("Current limit: {}", self.change_limit))],
);
}
let mut changes = Vec::new();
let changed_files: HashSet<&PathBuf> = HashSet::new();
for sym_ref in &filtered_refs {
let change = X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Replace,
sym_ref.file.clone(),
sym_ref.range,
new_name.to_string(),
format!("Rename '{}' to '{}'", old_name, new_name),
)
.with_original(old_name.to_string());
changes.push(change);
}
let files_count = filtered_refs
.iter()
.map(|r| &r.file)
.collect::<HashSet<_>>()
.len();
X86RefactoringResult::success(X86RefactoringKind::Rename, changes)
}
fn _is_valid_identifier(s: &str) -> bool {
if s.is_empty() {
return false;
}
let bytes = s.as_bytes();
if !Self::_is_ident_start(bytes[0]) {
return false;
}
for &b in &bytes[1..] {
if !Self::_is_ident_continue(b) {
return false;
}
}
true
}
pub fn extract_function(
&mut self,
file: &Path,
range: &X86RefactorRange,
new_function_name: &str,
return_type: Option<&str>,
) -> X86RefactoringResult {
if !Self::_is_valid_identifier(new_function_name) {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractFunction,
vec![X86RefactorError::error(format!(
"'{}' is not a valid identifier",
new_function_name
))],
);
}
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractFunction,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let extracted_code = match source.text_in_range(range) {
Some(t) => t,
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractFunction,
vec![X86RefactorError::error("Invalid range for extraction")],
);
}
};
let (params_in, params_out, local_vars) =
self._analyze_extracted_code_variables(&extracted_code);
let ret_type = return_type.unwrap_or("void").to_string();
let mut func_params = Vec::new();
for (i, (name, ty)) in params_in.iter().enumerate() {
func_params.push(X86ParameterDesc::new(name, ty, i));
}
for (i, (name, ty)) in params_out.iter().enumerate() {
let mut param = X86ParameterDesc::new(
format!("out_{}", name),
format!("{}*", ty),
params_in.len() + i,
);
param.is_const = false;
func_params.push(param);
}
let func_desc =
X86FunctionDesc::new(new_function_name, ret_type.clone(), file.to_path_buf());
let mut full_func = func_desc;
full_func.parameters = func_params;
let call_args: Vec<String> = params_in
.iter()
.map(|(name, _)| name.clone())
.chain(params_out.iter().map(|(name, _)| format!("&{}", name)))
.collect();
let call_text = if ret_type == "void" {
format!("{}({});", new_function_name, call_args.join(", "))
} else {
format!(
"{} {}({});",
ret_type,
new_function_name,
call_args.join(", ")
)
};
let func_def = format!(
"{} {}({}) {{\n{}\n}}",
ret_type,
new_function_name,
full_func
.parameters
.iter()
.map(|p| p.to_decl())
.collect::<Vec<_>>()
.join(", "),
extracted_code
);
let mut changes = Vec::new();
changes.push(
X86RefactoringChange::new(
X86RefactoringKind::ExtractFunction,
X86ChangeType::Replace,
file.to_path_buf(),
*range,
call_text,
format!(
"Replace extracted code with call to {}()",
new_function_name
),
)
.with_original(extracted_code),
);
let insert_loc = X86RefactorLocation::new(range.start.line, 1);
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ExtractFunction,
X86ChangeType::Insert,
file.to_path_buf(),
X86RefactorRange::new(insert_loc, insert_loc),
format!("{}\n\n", func_def),
format!("Insert new function {}", new_function_name),
));
X86RefactoringResult::success(X86RefactoringKind::ExtractFunction, changes)
}
fn _analyze_extracted_code_variables(
&self,
code: &str,
) -> (Vec<(String, String)>, Vec<(String, String)>, Vec<String>) {
let mut reads: Vec<(String, String)> = Vec::new(); let mut writes: Vec<(String, String)> = Vec::new();
let mut locals: Vec<String> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
let words: Vec<&str> = code
.split(|c: char| !c.is_alphanumeric() && c != '_')
.filter(|w| !w.is_empty())
.collect();
for word in &words {
if Self::_is_keyword(word) || word.is_empty() {
continue;
}
if !seen.insert(word.to_string()) {
continue;
}
if code.contains(&format!("int {}", word))
|| code.contains(&format!("float {}", word))
|| code.contains(&format!("double {}", word))
|| code.contains(&format!("char {}", word))
|| code.contains(&format!("auto {}", word))
|| code.contains(&format!("bool {}", word))
{
locals.push(word.to_string());
} else {
if code.contains(&format!("{} =", word))
|| code.contains(&format!("{} +=", word))
|| code.contains(&format!("{} -=", word))
|| code.contains(&format!("{} *=", word))
|| code.contains(&format!("{} /=", word))
|| code.contains(&format!("{}++", word))
|| code.contains(&format!("{}--", word))
|| code.contains(&format!("++{}", word))
|| code.contains(&format!("--{}", word))
{
writes.push((word.to_string(), "int".to_string()));
}
reads.push((word.to_string(), "int".to_string()));
}
}
reads.retain(|(name, _)| !locals.contains(name));
writes.retain(|(name, _)| !locals.contains(name));
let write_names: HashSet<&str> = writes.iter().map(|(n, _)| n.as_str()).collect();
reads.retain(|(name, _)| !write_names.contains(name.as_str()));
(reads, writes, locals)
}
pub fn extract_variable(
&mut self,
file: &Path,
range: &X86RefactorRange,
variable_name: &str,
variable_type: Option<&str>,
make_const: bool,
) -> X86RefactoringResult {
if !Self::_is_valid_identifier(variable_name) {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractVariable,
vec![X86RefactorError::error(format!(
"'{}' is not a valid identifier",
variable_name
))],
);
}
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractVariable,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let expression = match source.text_in_range(range) {
Some(t) => t,
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractVariable,
vec![X86RefactorError::error(
"Invalid range for expression extraction",
)],
);
}
};
let trimmed_expr = expression.trim().to_string();
let const_kw = if make_const { "const " } else { "" };
let var_type = variable_type.unwrap_or("auto").to_string();
let declaration = format!("{} {} = {};", const_kw, variable_name, trimmed_expr);
let replacement_text = if make_const {
variable_name.to_string()
} else {
format!("({})", variable_name)
};
let mut changes = Vec::new();
changes.push(
X86RefactoringChange::new(
X86RefactoringKind::ExtractVariable,
X86ChangeType::Replace,
file.to_path_buf(),
*range,
replacement_text,
format!("Replace expression with variable '{}'", variable_name),
)
.with_original(trimmed_expr.clone()),
);
let insert_loc = X86RefactorLocation::new(range.start.line, 1);
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ExtractVariable,
X86ChangeType::Insert,
file.to_path_buf(),
X86RefactorRange::new(insert_loc, insert_loc),
format!(" {}\n", declaration),
format!("Insert variable declaration for '{}'", variable_name),
));
X86RefactoringResult::success(X86RefactoringKind::ExtractVariable, changes)
}
pub fn inline_function(
&mut self,
function_name: &str,
file: &Path,
definition_range: Option<&X86RefactorRange>,
) -> X86RefactoringResult {
if function_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::InlineFunction,
vec![X86RefactorError::error("Function name must not be empty")],
);
}
if self.symbol_index.is_empty() {
self.index_symbols();
}
let def_refs: Vec<&X86SymbolRef> = self
.find_references(function_name)
.into_iter()
.filter(|r| r.is_definition && r.file == file)
.collect();
if def_refs.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::InlineFunction,
vec![X86RefactorError::error(format!(
"Function '{}' definition not found in {:?}",
function_name, file
))],
);
}
let call_sites: Vec<&X86SymbolRef> = self
.find_references(function_name)
.into_iter()
.filter(|r| !r.is_definition && !r.is_declaration)
.collect();
if call_sites.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::InlineFunction,
vec![X86RefactorError::warning(format!(
"No call sites found for '{}'",
function_name
))],
);
}
let mut changes = Vec::new();
for call in &call_sites {
let inline_text = format!(
"/* inlined {} */ {{\n /* body of {} */\n}}",
function_name, function_name
);
changes.push(X86RefactoringChange::new(
X86RefactoringKind::InlineFunction,
X86ChangeType::Replace,
call.file.clone(),
call.range,
inline_text,
format!("Inline call to '{}'", function_name),
));
}
if let Some(range) = definition_range {
changes.push(X86RefactoringChange::new(
X86RefactoringKind::InlineFunction,
X86ChangeType::Delete,
file.to_path_buf(),
*range,
String::new(),
format!("Remove definition of '{}'", function_name),
));
}
X86RefactoringResult::success(X86RefactoringKind::InlineFunction, changes)
}
pub fn inline_variable(&mut self, variable_name: &str, file: &Path) -> X86RefactoringResult {
if variable_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::InlineVariable,
vec![X86RefactorError::error("Variable name must not be empty")],
);
}
if self.symbol_index.is_empty() {
self.index_symbols();
}
let refs = self.find_references(variable_name);
if refs.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::InlineVariable,
vec![X86RefactorError::warning(format!(
"No references found for '{}'",
variable_name
))],
);
}
let decl = refs.iter().find(|r| r.is_declaration || r.is_definition);
if decl.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::InlineVariable,
vec![X86RefactorError::error(format!(
"Declaration/definition of '{}' not found",
variable_name
))],
);
}
let mut changes = Vec::new();
for sym_ref in &refs {
if sym_ref.is_definition || sym_ref.is_declaration {
changes.push(X86RefactoringChange::new(
X86RefactoringKind::InlineVariable,
X86ChangeType::Delete,
sym_ref.file.clone(),
sym_ref.range,
String::new(),
format!("Remove declaration of '{}'", variable_name),
));
} else {
changes.push(X86RefactoringChange::new(
X86RefactoringKind::InlineVariable,
X86ChangeType::Replace,
sym_ref.file.clone(),
sym_ref.range,
format!("/* {}_value */", variable_name),
format!("Inline use of '{}'", variable_name),
));
}
}
X86RefactoringResult::success(X86RefactoringKind::InlineVariable, changes)
}
pub fn move_definition(
&mut self,
symbol_name: &str,
source_file: &Path,
destination_file: &Path,
symbol_kind: X86SymbolKind,
) -> X86RefactoringResult {
if symbol_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::MoveDefinition,
vec![X86RefactorError::error("Symbol name must not be empty")],
);
}
let source = match self.get_file(source_file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::MoveDefinition,
vec![X86RefactorError::error(format!(
"Source file {:?} not loaded",
source_file
))],
);
}
};
if !self.files.contains_key(destination_file) {
if destination_file.exists() {
self.load_file(destination_file).ok();
}
}
let def_line = source
.lines
.iter()
.enumerate()
.find(|(_, line)| line.contains(symbol_name));
if def_line.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::MoveDefinition,
vec![X86RefactorError::error(format!(
"Symbol '{}' not found in {:?}",
symbol_name, source_file
))],
);
}
let (line_idx, _) = def_line.unwrap();
let line_num = line_idx + 1;
let line_content = source.lines[line_idx].clone();
let body_size = 10; let end_line = (line_num + body_size).min(source.line_count());
let definition_range = X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(end_line, source.line(end_line).map_or(1, |l| l.len() + 1)),
);
let definition_text = source
.text_in_range(&definition_range)
.unwrap_or_else(|| line_content.clone());
let mut changes = Vec::new();
changes.push(X86RefactoringChange::new(
X86RefactoringKind::MoveDefinition,
X86ChangeType::Delete,
source_file.to_path_buf(),
definition_range,
String::new(),
format!(
"Move {} '{}' definition from source",
symbol_kind, symbol_name
),
));
let dest = self.files.get(destination_file);
let insert_line = dest.map_or(1, |f| f.line_count() + 1);
let insert_loc = X86RefactorLocation::new(insert_line, 1);
changes.push(X86RefactoringChange::new(
X86RefactoringKind::MoveDefinition,
X86ChangeType::Insert,
destination_file.to_path_buf(),
X86RefactorRange::new(insert_loc, insert_loc),
format!("{}\n", definition_text),
format!("Insert {} '{}' into destination", symbol_kind, symbol_name),
));
X86RefactoringResult::success(X86RefactoringKind::MoveDefinition, changes)
}
pub fn change_signature(
&mut self,
function_name: &str,
file: &Path,
new_params: Vec<X86ParameterDesc>,
new_return_type: Option<&str>,
update_callers: bool,
) -> X86RefactoringResult {
if function_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::ChangeSignature,
vec![X86RefactorError::error("Function name must not be empty")],
);
}
let mut seen_names: HashSet<&str> = HashSet::new();
for param in &new_params {
if !param.is_variadic && param.name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::ChangeSignature,
vec![X86RefactorError::error(
"All non-variadic parameters must have names",
)],
);
}
if !param.is_variadic && !seen_names.insert(¶m.name) {
return X86RefactoringResult::failure(
X86RefactoringKind::ChangeSignature,
vec![X86RefactorError::error(format!(
"Duplicate parameter name '{}'",
param.name
))],
);
}
}
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::ChangeSignature,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let def_found = source
.lines
.iter()
.enumerate()
.find(|(_, line)| line.contains(function_name) && line.contains('('));
if def_found.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::ChangeSignature,
vec![X86RefactorError::error(format!(
"Function '{}' not found in {:?}",
function_name, file
))],
);
}
let (line_idx, _) = def_found.unwrap();
let line_num = line_idx + 1;
let old_line = source.lines[line_idx].clone();
let param_str: Vec<String> = new_params.iter().map(|p| p.to_source()).collect();
let ret = new_return_type.unwrap_or("void");
let new_sig = format!("{} {}({})", ret, function_name, param_str.join(", "));
let mut changes = Vec::new();
let sig_range = X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, old_line.len() + 1),
);
changes.push(
X86RefactoringChange::new(
X86RefactoringKind::ChangeSignature,
X86ChangeType::Replace,
file.to_path_buf(),
sig_range,
new_sig,
format!("Change signature of '{}'", function_name),
)
.with_original(old_line),
);
if update_callers {
if self.symbol_index.is_empty() {
self.index_symbols();
}
let call_sites = self.find_references(function_name);
for call in call_sites {
if !call.is_definition && !call.is_declaration {
let new_call_args: Vec<String> = new_params
.iter()
.filter(|p| !p.is_variadic)
.map(|p| {
if p.has_default {
p.default_value.clone()
} else {
format!("/* {} */", p.name)
}
})
.collect();
let new_call = format!("{}({})", function_name, new_call_args.join(", "));
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ChangeSignature,
X86ChangeType::Replace,
call.file.clone(),
call.range,
new_call,
format!("Update caller of '{}'", function_name),
));
}
}
}
X86RefactoringResult::success(X86RefactoringKind::ChangeSignature, changes)
}
pub fn add_parameter(
&mut self,
function_name: &str,
file: &Path,
param: X86ParameterDesc,
update_callers: bool,
) -> X86RefactoringResult {
if function_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::AddParameter,
vec![X86RefactorError::error("Function name must not be empty")],
);
}
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::AddParameter,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let def_line_idx = source
.lines
.iter()
.enumerate()
.find(|(_, line)| line.contains(function_name) && line.contains('('));
if def_line_idx.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::AddParameter,
vec![X86RefactorError::error(format!(
"Function '{}' not found in {:?}",
function_name, file
))],
);
}
let (line_idx, _) = def_line_idx.unwrap();
let line_num = line_idx + 1;
let old_line = source.lines[line_idx].clone();
let close_paren_pos = old_line.rfind(')').unwrap_or(old_line.len());
let before_paren = &old_line[..close_paren_pos];
let after_paren = &old_line[close_paren_pos..];
let insert_text = if before_paren.ends_with('(') {
param.to_source()
} else {
format!(", {}", param.to_source())
};
let new_line = format!("{}{}{}", before_paren, insert_text, after_paren);
let mut changes = Vec::new();
let sig_range = X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, old_line.len() + 1),
);
changes.push(
X86RefactoringChange::new(
X86RefactoringKind::AddParameter,
X86ChangeType::Replace,
file.to_path_buf(),
sig_range,
new_line.clone(),
format!("Add parameter '{}' to '{}'", param.name, function_name),
)
.with_original(old_line),
);
if update_callers {
if self.symbol_index.is_empty() {
self.index_symbols();
}
let call_sites = self.find_references(function_name);
for call in call_sites {
if !call.is_definition && !call.is_declaration {
let default_val = if param.has_default {
param.default_value.clone()
} else {
format!("/* {} */", param.name)
};
let call_context = &call.context;
let insert_pos = call_context.rfind(')').unwrap_or(call_context.len());
let prefix = &call_context[..insert_pos];
let suffix = &call_context[insert_pos..];
let new_call = if prefix.ends_with('(') {
format!("{}{}{}", prefix, default_val, suffix)
} else {
format!("{}, {}{}", prefix, default_val, suffix)
};
changes.push(X86RefactoringChange::new(
X86RefactoringKind::AddParameter,
X86ChangeType::Replace,
call.file.clone(),
call.range,
new_call,
format!("Update caller of '{}' with new param", function_name),
));
}
}
}
X86RefactoringResult::success(X86RefactoringKind::AddParameter, changes)
}
pub fn remove_parameter(
&mut self,
function_name: &str,
file: &Path,
param_position: usize,
update_callers: bool,
) -> X86RefactoringResult {
if function_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::RemoveParameter,
vec![X86RefactorError::error("Function name must not be empty")],
);
}
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::RemoveParameter,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let def_line_idx = source
.lines
.iter()
.enumerate()
.find(|(_, line)| line.contains(function_name) && line.contains('('));
if def_line_idx.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::RemoveParameter,
vec![X86RefactorError::error(format!(
"Function '{}' not found in {:?}",
function_name, file
))],
);
}
let (line_idx, _) = def_line_idx.unwrap();
let line_num = line_idx + 1;
let old_line = source.lines[line_idx].clone();
let paren_start = old_line.find('(').unwrap_or(0);
let paren_end = old_line.rfind(')').unwrap_or(old_line.len());
let params_str = if paren_start + 1 < paren_end {
&old_line[paren_start + 1..paren_end]
} else {
""
};
let mut params: Vec<String> = params_str
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
if param_position >= params.len() {
return X86RefactoringResult::failure(
X86RefactoringKind::RemoveParameter,
vec![X86RefactorError::error(format!(
"Parameter position {} out of range (0..{})",
param_position,
params.len()
))],
);
}
let removed = params.remove(param_position);
let new_params_str = params.join(", ");
let new_line = format!(
"{}({}){}",
&old_line[..=paren_start],
new_params_str,
&old_line[paren_end..]
);
let mut changes = Vec::new();
let sig_range = X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, old_line.len() + 1),
);
changes.push(
X86RefactoringChange::new(
X86RefactoringKind::RemoveParameter,
X86ChangeType::Replace,
file.to_path_buf(),
sig_range,
new_line,
format!("Remove parameter '{}' from '{}'", removed, function_name),
)
.with_original(old_line),
);
if update_callers {
if self.symbol_index.is_empty() {
self.index_symbols();
}
let call_sites = self.find_references(function_name);
for call in call_sites {
if !call.is_definition && !call.is_declaration {
let call_ctx = &call.context;
let args_start = call_ctx.find('(').unwrap_or(0);
let args_end = call_ctx.rfind(')').unwrap_or(call_ctx.len());
let args_str = if args_start + 1 < args_end {
&call_ctx[args_start + 1..args_end]
} else {
""
};
let mut args: Vec<&str> = args_str
.split(',')
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.collect();
if param_position < args.len() {
args.remove(param_position);
}
let new_call = format!(
"{}({}){}",
&call_ctx[..=args_start],
args.join(", "),
&call_ctx[args_end..]
);
changes.push(X86RefactoringChange::new(
X86RefactoringKind::RemoveParameter,
X86ChangeType::Replace,
call.file.clone(),
call.range,
new_call,
format!("Update caller of '{}'", function_name),
));
}
}
}
X86RefactoringResult::success(X86RefactoringKind::RemoveParameter, changes)
}
pub fn extract_interface(
&mut self,
class_name: &str,
file: &Path,
interface_name: &str,
methods_to_extract: &[String],
destination_file: Option<&Path>,
) -> X86RefactoringResult {
if class_name.is_empty() || interface_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractInterface,
vec![X86RefactorError::error(
"Class name and interface name must not be empty",
)],
);
}
if methods_to_extract.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractInterface,
vec![X86RefactorError::error(
"Must specify at least one method to extract",
)],
);
}
if !Self::_is_valid_identifier(interface_name) {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractInterface,
vec![X86RefactorError::error(format!(
"'{}' is not a valid identifier",
interface_name
))],
);
}
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractInterface,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let class_line = source
.lines
.iter()
.enumerate()
.find(|(_, line)| line.contains("class") && line.contains(class_name));
if class_line.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractInterface,
vec![X86RefactorError::error(format!(
"Class '{}' not found in {:?}",
class_name, file
))],
);
}
let (_, class_def_line) = class_line.unwrap();
let mut interface_code = format!("class {} {{\npublic:\n", interface_name);
for method in methods_to_extract {
interface_code.push_str(&format!(" virtual {} = 0;\n", method));
}
interface_code.push_str(" virtual ~");
interface_code.push_str(interface_name);
interface_code.push_str("() = default;\n");
interface_code.push_str("};\n");
let modified_class_line = if class_def_line.contains(':') {
class_def_line.replace(
&format!("class {}", class_name),
&format!("class {} : public {}", class_name, interface_name),
)
} else {
class_def_line.replace(
&format!("class {}", class_name),
&format!("class {} : public {}", class_name, interface_name),
)
};
let (line_idx, _) = class_line.unwrap();
let line_num = line_idx + 1;
let mut changes = Vec::new();
let dest_file = destination_file.unwrap_or(file).to_path_buf();
let insert_loc = X86RefactorLocation::new(line_num, 1);
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ExtractInterface,
X86ChangeType::Insert,
dest_file.clone(),
X86RefactorRange::new(insert_loc, insert_loc),
format!("{}\n", interface_code),
format!("Insert interface '{}'", interface_name),
));
let class_line_range = X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, class_def_line.len() + 1),
);
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ExtractInterface,
X86ChangeType::Replace,
file.to_path_buf(),
class_line_range,
modified_class_line,
format!("Make '{}' inherit from '{}'", class_name, interface_name),
));
for method in methods_to_extract {
for (ml_idx, method_line) in source.lines.iter().enumerate() {
if method_line.contains(method) && ml_idx != line_idx {
let ml_num = ml_idx + 1;
let modified_method =
if method_line.trim().ends_with('{') || method_line.trim().ends_with(';') {
let trimmed = method_line.trim_end();
let suffix = if trimmed.ends_with('{') { " {" } else { ";" };
let base = &trimmed[..trimmed.len() - 1];
format!("{} override{}", base, suffix)
} else {
format!("{} override", method_line)
};
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ExtractInterface,
X86ChangeType::Replace,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(ml_num, 1),
X86RefactorLocation::new(ml_num, method_line.len() + 1),
),
modified_method,
format!("Add 'override' to '{}'", method),
));
break;
}
}
}
X86RefactoringResult::success(X86RefactoringKind::ExtractInterface, changes)
}
pub fn extract_class(
&mut self,
class_name: &str,
file: &Path,
new_base_name: &str,
members_to_move: &[String],
destination_file: Option<&Path>,
) -> X86RefactoringResult {
if class_name.is_empty() || new_base_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractClass,
vec![X86RefactorError::error(
"Class name and base class name must not be empty",
)],
);
}
if members_to_move.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractClass,
vec![X86RefactorError::error(
"Must specify at least one member to move to base class",
)],
);
}
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractClass,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let class_line = source.lines.iter().enumerate().find(|(_, line)| {
(line.contains("class ") || line.contains("struct ")) && line.contains(class_name)
});
if class_line.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractClass,
vec![X86RefactorError::error(format!(
"Class '{}' not found in {:?}",
class_name, file
))],
);
}
let (line_idx, class_def_line) = class_line.unwrap();
let line_num = line_idx + 1;
let mut base_code = format!("class {} {{\npublic:\n", new_base_name);
for member in members_to_move {
for src_line in &source.lines[line_idx..] {
if src_line.contains(member) {
base_code.push_str(&format!(" {}\n", src_line.trim()));
break;
}
}
}
base_code.push_str("};\n");
let mut changes = Vec::new();
let dest_file = destination_file.unwrap_or(file).to_path_buf();
let insert_loc = X86RefactorLocation::new(line_num, 1);
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ExtractClass,
X86ChangeType::Insert,
dest_file,
X86RefactorRange::new(insert_loc, insert_loc),
format!("{}\n", base_code),
format!("Insert base class '{}'", new_base_name),
));
let modified_line = if class_def_line.contains(':') {
class_def_line.replace(
class_name,
&format!("{} : public {}", class_name, new_base_name),
)
} else {
let brace_pos = class_def_line.find('{').unwrap_or(class_def_line.len());
format!(
"{} : public {} {}",
&class_def_line[..brace_pos],
new_base_name,
&class_def_line[brace_pos..]
)
};
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ExtractClass,
X86ChangeType::Replace,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, class_def_line.len() + 1),
),
modified_line,
format!("Make '{}' inherit from '{}'", class_name, new_base_name),
));
X86RefactoringResult::success(X86RefactoringKind::ExtractClass, changes)
}
pub fn pull_up(
&mut self,
derived_class_name: &str,
base_class_name: &str,
file: &Path,
members_to_pull: &[String],
) -> X86RefactoringResult {
if derived_class_name.is_empty() || base_class_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::PullUp,
vec![X86RefactorError::error(
"Derived and base class names must not be empty",
)],
);
}
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::PullUp,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let base_line = source
.lines
.iter()
.enumerate()
.find(|(_, line)| line.contains("class ") && line.contains(base_class_name));
if base_line.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::PullUp,
vec![X86RefactorError::error(format!(
"Base class '{}' not found",
base_class_name
))],
);
}
let (base_line_idx, _) = base_line.unwrap();
let base_line_num = base_line_idx + 1;
let mut changes = Vec::new();
for member in members_to_pull {
let member_line = source
.lines
.iter()
.enumerate()
.skip(base_line_idx)
.find(|(_, line)| line.contains(member));
if let Some((ml_idx, ml_text)) = member_line {
let ml_num = ml_idx + 1;
changes.push(X86RefactoringChange::new(
X86RefactoringKind::PullUp,
X86ChangeType::Delete,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(ml_num, 1),
X86RefactorLocation::new(ml_num, ml_text.len() + 1),
),
String::new(),
format!(
"Remove '{}' from derived class '{}'",
member, derived_class_name
),
));
let insert_line = base_line_num + 1;
changes.push(X86RefactoringChange::new(
X86RefactoringKind::PullUp,
X86ChangeType::Insert,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(insert_line, 1),
X86RefactorLocation::new(insert_line, 1),
),
format!(" {}\n", ml_text.trim()),
format!("Add '{}' to base class '{}'", member, base_class_name),
));
}
}
X86RefactoringResult::success(X86RefactoringKind::PullUp, changes)
}
pub fn push_down(
&mut self,
base_class_name: &str,
derived_class_name: &str,
file: &Path,
members_to_push: &[String],
) -> X86RefactoringResult {
if base_class_name.is_empty() || derived_class_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::PushDown,
vec![X86RefactorError::error(
"Base and derived class names must not be empty",
)],
);
}
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::PushDown,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let mut changes = Vec::new();
for member in members_to_push {
let member_line = source
.lines
.iter()
.enumerate()
.find(|(_, line)| line.contains(member));
if let Some((ml_idx, ml_text)) = member_line {
let ml_num = ml_idx + 1;
changes.push(X86RefactoringChange::new(
X86RefactoringKind::PushDown,
X86ChangeType::Delete,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(ml_num, 1),
X86RefactorLocation::new(ml_num, ml_text.len() + 1),
),
String::new(),
format!("Remove '{}' from base class '{}'", member, base_class_name),
));
let derived_line =
source.lines.iter().enumerate().find(|(_, line)| {
line.contains("class ") && line.contains(derived_class_name)
});
if let Some((dl_idx, _)) = derived_line {
let insert_line = dl_idx + 2;
changes.push(X86RefactoringChange::new(
X86RefactoringKind::PushDown,
X86ChangeType::Insert,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(insert_line, 1),
X86RefactorLocation::new(insert_line, 1),
),
format!(" {}\n", ml_text.trim()),
format!("Add '{}' to derived class '{}'", member, derived_class_name),
));
}
}
}
X86RefactoringResult::success(X86RefactoringKind::PushDown, changes)
}
pub fn replace_type(
&mut self,
old_type: &str,
new_type: &str,
file_filter: Option<&Path>,
include_declarations: bool,
include_casts: bool,
include_templates: bool,
) -> X86RefactoringResult {
if old_type.is_empty() || new_type.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::ReplaceType,
vec![X86RefactorError::error(
"Old type and new type must not be empty",
)],
);
}
if !Self::_is_valid_identifier(new_type) && new_type.contains('<') {
} else if !Self::_is_valid_identifier(new_type) {
return X86RefactoringResult::failure(
X86RefactoringKind::ReplaceType,
vec![X86RefactorError::error(format!(
"'{}' is not a valid type name",
new_type
))],
);
}
let mut changes = Vec::new();
let files_to_process: Vec<PathBuf> = if let Some(ff) = file_filter {
vec![ff.to_path_buf()]
} else {
self.files.keys().cloned().collect()
};
for file_path in &files_to_process {
if let Some(file) = self.files.get(file_path) {
for (line_idx, line) in file.lines.iter().enumerate() {
let line_num = line_idx + 1;
if line.trim().starts_with("//") || line.trim().starts_with("/*") {
continue;
}
let mut search_start = 0;
while let Some(pos) = line[search_start..].find(old_type) {
let abs_pos = search_start + pos;
let abs_end = abs_pos + old_type.len();
let before_ok =
abs_pos == 0 || !Self::_is_ident_continue(line.as_bytes()[abs_pos - 1]);
let after_ok = abs_end >= line.len()
|| !Self::_is_ident_continue(line.as_bytes()[abs_end]);
if before_ok && after_ok {
changes.push(
X86RefactoringChange::new(
X86RefactoringKind::ReplaceType,
X86ChangeType::Replace,
file_path.clone(),
X86RefactorRange::new(
X86RefactorLocation::new(line_num, abs_pos + 1),
X86RefactorLocation::new(line_num, abs_end + 1),
),
new_type.to_string(),
format!("Replace type '{}' with '{}'", old_type, new_type),
)
.with_original(old_type.to_string()),
);
}
search_start = abs_end;
if search_start >= line.len() {
break;
}
}
}
}
}
if changes.is_empty() {
X86RefactoringResult::failure(
X86RefactoringKind::ReplaceType,
vec![X86RefactorError::warning(format!(
"No occurrences of type '{}' found",
old_type
))],
)
} else {
X86RefactoringResult::success(X86RefactoringKind::ReplaceType, changes)
}
}
pub fn reorder_parameters(
&mut self,
function_name: &str,
file: &Path,
new_order: &[usize],
update_callers: bool,
) -> X86RefactoringResult {
if function_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::ReorderParameters,
vec![X86RefactorError::error("Function name must not be empty")],
);
}
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::ReorderParameters,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let def_line = source
.lines
.iter()
.enumerate()
.find(|(_, line)| line.contains(function_name) && line.contains('('));
if def_line.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::ReorderParameters,
vec![X86RefactorError::error(format!(
"Function '{}' not found",
function_name
))],
);
}
let (line_idx, _) = def_line.unwrap();
let line_num = line_idx + 1;
let old_line = source.lines[line_idx].clone();
let paren_start = old_line.find('(').unwrap_or(0);
let paren_end = old_line.rfind(')').unwrap_or(old_line.len());
let params_str = if paren_start + 1 < paren_end {
&old_line[paren_start + 1..paren_end]
} else {
""
};
let original_params: Vec<String> = params_str
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
if new_order.len() != original_params.len() {
return X86RefactoringResult::failure(
X86RefactoringKind::ReorderParameters,
vec![X86RefactorError::error(format!(
"New order length ({}) does not match parameter count ({})",
new_order.len(),
original_params.len()
))],
);
}
let mut seen: HashSet<usize> = HashSet::new();
for &i in new_order {
if i >= original_params.len() {
return X86RefactoringResult::failure(
X86RefactoringKind::ReorderParameters,
vec![X86RefactorError::error(format!(
"Invalid index {} for {} parameters",
i,
original_params.len()
))],
);
}
if !seen.insert(i) {
return X86RefactoringResult::failure(
X86RefactoringKind::ReorderParameters,
vec![X86RefactorError::error(format!(
"Duplicate index {} in permutation",
i
))],
);
}
}
let reordered: Vec<String> = new_order
.iter()
.map(|&i| original_params[i].clone())
.collect();
let new_params_str = reordered.join(", ");
let new_line = format!(
"{}({}){}",
&old_line[..=paren_start],
new_params_str,
&old_line[paren_end..]
);
let mut changes = Vec::new();
changes.push(
X86RefactoringChange::new(
X86RefactoringKind::ReorderParameters,
X86ChangeType::Replace,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, old_line.len() + 1),
),
new_line,
format!("Reorder parameters of '{}'", function_name),
)
.with_original(old_line),
);
if update_callers {
if self.symbol_index.is_empty() {
self.index_symbols();
}
let calls = self.find_references(function_name);
for call in calls {
if !call.is_definition && !call.is_declaration {
let ctx = &call.context;
let args_start = ctx.find('(').unwrap_or(0);
let args_end = ctx.rfind(')').unwrap_or(ctx.len());
let args_str = if args_start + 1 < args_end {
&ctx[args_start + 1..args_end]
} else {
""
};
let args: Vec<&str> = args_str
.split(',')
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.collect();
let reordered_args: Vec<&&str> = new_order
.iter()
.map(|&i| &args[i.min(args.len().saturating_sub(1))])
.collect();
let new_call = format!(
"{}({}){}",
&ctx[..=args_start],
reordered_args
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(", "),
&ctx[args_end..]
);
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ReorderParameters,
X86ChangeType::Replace,
call.file.clone(),
call.range,
new_call,
format!("Update caller of reordered '{}'", function_name),
));
}
}
}
X86RefactoringResult::success(X86RefactoringKind::ReorderParameters, changes)
}
pub fn replace_macro(
&mut self,
macro_name: &str,
file: &Path,
replacement_kind: &str, replacement_body: &str,
) -> X86RefactoringResult {
if macro_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::ReplaceMacro,
vec![X86RefactorError::error("Macro name must not be empty")],
);
}
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::ReplaceMacro,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let def_line = source
.lines
.iter()
.enumerate()
.find(|(_, line)| line.trim().starts_with("#define") && line.contains(macro_name));
if def_line.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::ReplaceMacro,
vec![X86RefactorError::error(format!(
"Macro '#define {}' not found",
macro_name
))],
);
}
let (def_line_idx, def_text) = def_line.unwrap();
let def_line_num = def_line_idx + 1;
let replacement_decl = match replacement_kind {
"constexpr" => format!("constexpr auto {} = {};", macro_name, replacement_body),
"inline_function" => format!(
"inline auto {}(int x) {{ return {}; }}",
macro_name, replacement_body
),
"enum" => format!("enum {{ {} = {} }};", macro_name, replacement_body),
_ => format!("constexpr auto {} = {};", macro_name, replacement_body),
};
let mut changes = Vec::new();
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ReplaceMacro,
X86ChangeType::Delete,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(def_line_num, 1),
X86RefactorLocation::new(def_line_num, def_text.len() + 1),
),
String::new(),
format!("Remove #define {}", macro_name),
));
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ReplaceMacro,
X86ChangeType::Insert,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(def_line_num, 1),
X86RefactorLocation::new(def_line_num, 1),
),
format!("{}\n", replacement_decl),
format!(
"Insert {} replacement for '{}'",
replacement_kind, macro_name
),
));
X86RefactoringResult::success(X86RefactoringKind::ReplaceMacro, changes)
}
pub fn expand_macro(
&mut self,
macro_name: &str,
_file: &Path,
expansion_text: &str,
) -> X86RefactoringResult {
if macro_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::ExpandMacro,
vec![X86RefactorError::error("Macro name must not be empty")],
);
}
if self.symbol_index.is_empty() {
self.index_symbols();
}
let refs = self.find_references(macro_name);
if refs.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::ExpandMacro,
vec![X86RefactorError::warning(format!(
"No references found for macro '{}'",
macro_name
))],
);
}
let mut changes = Vec::new();
for sym_ref in &refs {
if sym_ref.is_definition {
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ExpandMacro,
X86ChangeType::Delete,
sym_ref.file.clone(),
sym_ref.range,
String::new(),
format!("Remove #define {}", macro_name),
));
} else {
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ExpandMacro,
X86ChangeType::Replace,
sym_ref.file.clone(),
sym_ref.range,
expansion_text.to_string(),
format!("Expand macro '{}'", macro_name),
));
}
}
X86RefactoringResult::success(X86RefactoringKind::ExpandMacro, changes)
}
pub fn extract_method(
&mut self,
class_name: &str,
file: &Path,
range: &X86RefactorRange,
new_method_name: &str,
return_type: Option<&str>,
) -> X86RefactoringResult {
if !Self::_is_valid_identifier(new_method_name) {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractMethod,
vec![X86RefactorError::error(format!(
"'{}' is not a valid identifier",
new_method_name
))],
);
}
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractMethod,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let extracted_code = match source.text_in_range(range) {
Some(t) => t,
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::ExtractMethod,
vec![X86RefactorError::error(
"Invalid range for method extraction",
)],
);
}
};
let (params_in, _params_out, _locals) =
self._analyze_extracted_code_variables(&extracted_code);
let ret = return_type.unwrap_or("void").to_string();
let param_str: Vec<String> = params_in
.iter()
.map(|(name, ty)| format!("{} {}", ty, name))
.collect();
let method_def = format!(
"{} {}::{}({}) {{\n{}\n}}",
ret,
class_name,
new_method_name,
param_str.join(", "),
extracted_code
);
let call_text = if ret == "void" {
let call_args: Vec<String> = params_in.iter().map(|(name, _)| name.clone()).collect();
format!("this->{}({});", new_method_name, call_args.join(", "))
} else {
let call_args: Vec<String> = params_in.iter().map(|(name, _)| name.clone()).collect();
format!(
"auto _result = this->{}({});",
new_method_name,
call_args.join(", ")
)
};
let mut changes = Vec::new();
changes.push(
X86RefactoringChange::new(
X86RefactoringKind::ExtractMethod,
X86ChangeType::Replace,
file.to_path_buf(),
*range,
call_text,
format!(
"Replace extracted code with {}::{}()",
class_name, new_method_name
),
)
.with_original(extracted_code),
);
let insert_loc = X86RefactorLocation::new(range.start.line, 1);
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ExtractMethod,
X86ChangeType::Insert,
file.to_path_buf(),
X86RefactorRange::new(insert_loc, insert_loc),
format!("{}\n\n", method_def),
format!("Insert method {}::{}()", class_name, new_method_name),
));
X86RefactoringResult::success(X86RefactoringKind::ExtractMethod, changes)
}
pub fn convert_to_lambda(&mut self, function_name: &str, file: &Path) -> X86RefactoringResult {
if function_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::ConvertToLambda,
vec![X86RefactorError::error("Function name must not be empty")],
);
}
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::ConvertToLambda,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let def_line = source
.lines
.iter()
.enumerate()
.find(|(_, line)| line.contains(function_name));
if def_line.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::ConvertToLambda,
vec![X86RefactorError::error(format!(
"Function '{}' not found",
function_name
))],
);
}
let (line_idx, def_text) = def_line.unwrap();
let line_num = line_idx + 1;
let lambda_text = format!(
"auto {} = [&]() {{\n // converted from function\n}};",
function_name
);
let mut changes = Vec::new();
changes.push(
X86RefactoringChange::new(
X86RefactoringKind::ConvertToLambda,
X86ChangeType::Replace,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, def_text.len() + 1),
),
lambda_text,
format!("Convert '{}' to lambda", function_name),
)
.with_original(def_text.clone()),
);
X86RefactoringResult::success(X86RefactoringKind::ConvertToLambda, changes)
}
pub fn convert_from_lambda(
&mut self,
lambda_var_name: &str,
file: &Path,
new_function_name: &str,
) -> X86RefactoringResult {
if lambda_var_name.is_empty() || new_function_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::ConvertFromLambda,
vec![X86RefactorError::error(
"Lambda and function names must not be empty",
)],
);
}
if !Self::_is_valid_identifier(new_function_name) {
return X86RefactoringResult::failure(
X86RefactoringKind::ConvertFromLambda,
vec![X86RefactorError::error(format!(
"'{}' is not a valid identifier",
new_function_name
))],
);
}
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::ConvertFromLambda,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let lambda_line = source
.lines
.iter()
.enumerate()
.find(|(_, line)| line.contains(lambda_var_name) && line.contains('='));
if lambda_line.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::ConvertFromLambda,
vec![X86RefactorError::error(format!(
"Lambda variable '{}' not found",
lambda_var_name
))],
);
}
let (line_idx, def_text) = lambda_line.unwrap();
let line_num = line_idx + 1;
let func_decl = format!(
"auto {}(/* params */) {{\n // converted from lambda\n}}",
new_function_name
);
let mut changes = Vec::new();
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ConvertFromLambda,
X86ChangeType::Replace,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, def_text.len() + 1),
),
format!("auto {} = &{};", lambda_var_name, new_function_name),
format!(
"Convert lambda '{}' to named function '{}'",
lambda_var_name, new_function_name
),
));
changes.push(X86RefactoringChange::new(
X86RefactoringKind::ConvertFromLambda,
X86ChangeType::Insert,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, 1),
),
format!("{}\n", func_decl),
format!("Insert function '{}'", new_function_name),
));
X86RefactoringResult::success(X86RefactoringKind::ConvertFromLambda, changes)
}
pub fn add_override(
&mut self,
class_name: &str,
file: &Path,
method_names: &[String],
) -> X86RefactoringResult {
if class_name.is_empty() {
return X86RefactoringResult::failure(
X86RefactoringKind::AddOverride,
vec![X86RefactorError::error("Class name must not be empty")],
);
}
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::AddOverride,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let mut changes = Vec::new();
for method in method_names {
for (line_idx, line) in source.lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.contains(method)
&& trimmed.contains('(')
&& !trimmed.contains("override")
{
let line_num = line_idx + 1;
let modified = if trimmed.ends_with('{') {
format!("{} override {{", &trimmed[..trimmed.len() - 1].trim_end())
} else if trimmed.ends_with(';') {
format!("{} override;", &trimmed[..trimmed.len() - 1].trim_end())
} else {
format!("{} override", trimmed)
};
changes.push(X86RefactoringChange::new(
X86RefactoringKind::AddOverride,
X86ChangeType::Replace,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, line.len() + 1),
),
modified,
format!("Add 'override' to {}::{}", class_name, method),
));
break;
}
}
}
if changes.is_empty() {
X86RefactoringResult::failure(
X86RefactoringKind::AddOverride,
vec![X86RefactorError::warning(
"No methods found without override",
)],
)
} else {
X86RefactoringResult::success(X86RefactoringKind::AddOverride, changes)
}
}
pub fn simplify_qualifications(
&mut self,
file: &Path,
namespace_prefixes: &[String],
) -> X86RefactoringResult {
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::SimplifyQualifications,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let mut changes = Vec::new();
for prefix in namespace_prefixes {
let prefix_with_colons = format!("{}::", prefix);
for (line_idx, line) in source.lines.iter().enumerate() {
let mut search_start = 0;
while let Some(pos) = line[search_start..].find(&prefix_with_colons) {
let abs_start = search_start + pos;
let abs_end = abs_start + prefix_with_colons.len();
let line_num = line_idx + 1;
changes.push(
X86RefactoringChange::new(
X86RefactoringKind::SimplifyQualifications,
X86ChangeType::Replace,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(line_num, abs_start + 1),
X86RefactorLocation::new(line_num, abs_end + 1),
),
String::new(),
format!("Remove redundant '{}::' prefix", prefix),
)
.with_original(prefix_with_colons.clone()),
);
search_start = abs_end;
if search_start >= line.len() {
break;
}
}
}
}
if changes.is_empty() {
X86RefactoringResult::failure(
X86RefactoringKind::SimplifyQualifications,
vec![X86RefactorError::warning(
"No redundant qualifications found",
)],
)
} else {
X86RefactoringResult::success(X86RefactoringKind::SimplifyQualifications, changes)
}
}
pub fn undo_last(&mut self) -> Result<(), X86RefactorError> {
if let Some(last) = self.operation_history.last() {
if !last.reversible {
return Err(X86RefactorError::error("Last operation is not reversible"));
}
let mut reversed_changes = Vec::new();
for change in &last.changes {
let reversed = match change.change_type {
X86ChangeType::Replace => X86RefactoringChange::new(
change.kind,
X86ChangeType::Replace,
change.file.clone(),
change.range,
change.original_text.clone().unwrap_or_default(),
format!("Undo: {}", change.description),
),
X86ChangeType::Insert => X86RefactoringChange::new(
change.kind,
X86ChangeType::Delete,
change.file.clone(),
change.range,
String::new(),
format!("Undo: {}", change.description),
),
X86ChangeType::Delete => X86RefactoringChange::new(
change.kind,
X86ChangeType::Insert,
change.file.clone(),
change.range,
change.original_text.clone().unwrap_or_default(),
format!("Undo: {}", change.description),
),
_ => continue,
};
reversed_changes.push(reversed);
}
self.changes = reversed_changes;
self.apply_changes()?;
self.operation_history.pop();
Ok(())
} else {
Err(X86RefactorError::error("No operation to undo"))
}
}
pub fn pending_change_count(&self) -> usize {
self.changes.len()
}
pub fn loaded_file_count(&self) -> usize {
self.files.len()
}
pub fn indexed_symbol_count(&self) -> usize {
self.symbol_index.len()
}
}
#[derive(Debug, Clone)]
pub struct X86RefactorPreview {
pub engine: X86Refactor,
pub diffs: Vec<X86DiffEntry>,
pub impact: X86ImpactAnalysis,
pub conflicts: Vec<X86ConflictInfo>,
pub summary: X86PreviewSummary,
}
#[derive(Debug, Clone)]
pub struct X86DiffEntry {
pub file: PathBuf,
pub start_line: usize,
pub end_line: usize,
pub original: String,
pub modified: String,
pub diff_type: X86DiffType,
pub description: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DiffType {
Added,
Removed,
Modified,
}
impl fmt::Display for X86DiffType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Added => write!(f, "+"),
Self::Removed => write!(f, "-"),
Self::Modified => write!(f, "~"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86ImpactAnalysis {
pub files_affected: usize,
pub lines_changed: usize,
pub insertions: usize,
pub deletions: usize,
pub modifications: usize,
pub per_file: HashMap<PathBuf, X86FileImpact>,
pub multi_tu: bool,
pub affects_headers: bool,
pub affects_templates: bool,
pub risk_level: X86RefactorRisk,
}
#[derive(Debug, Clone)]
pub struct X86FileImpact {
pub file: PathBuf,
pub lines_changed: usize,
pub insertions: usize,
pub deletions: usize,
pub modifications: usize,
pub is_header: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum X86RefactorRisk {
Trivial,
Low,
Medium,
High,
Critical,
}
impl fmt::Display for X86RefactorRisk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Trivial => write!(f, "trivial"),
Self::Low => write!(f, "low"),
Self::Medium => write!(f, "medium"),
Self::High => write!(f, "high"),
Self::Critical => write!(f, "critical"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86ConflictInfo {
pub change_a: String,
pub change_b: String,
pub file: PathBuf,
pub overlapping_range: X86RefactorRange,
pub auto_resolvable: bool,
pub resolution: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86PreviewSummary {
pub total_changes: usize,
pub files_to_modify: usize,
pub lines_added: usize,
pub lines_removed: usize,
pub conflicts_found: usize,
pub estimated_ms: u64,
pub validation_passed: bool,
}
impl X86RefactorPreview {
pub fn new(engine: X86Refactor) -> Self {
let mut preview = Self {
engine,
diffs: Vec::new(),
impact: X86ImpactAnalysis::default(),
conflicts: Vec::new(),
summary: X86PreviewSummary::default(),
};
preview.generate();
preview
}
pub fn generate(&mut self) {
self.generate_diffs();
self.analyze_impact();
self.detect_conflicts();
self.build_summary();
}
pub fn generate_diffs(&mut self) {
self.diffs.clear();
for change in &self.engine.changes {
let original = change
.original_text
.clone()
.unwrap_or_else(|| "<unknown>".to_string());
let diff_type = match change.change_type {
X86ChangeType::Insert | X86ChangeType::CreateFile => X86DiffType::Added,
X86ChangeType::Delete | X86ChangeType::DeleteFile => X86DiffType::Removed,
_ => X86DiffType::Modified,
};
self.diffs.push(X86DiffEntry {
file: change.file.clone(),
start_line: change.range.start.line,
end_line: change.range.end.line,
original,
modified: change.new_text.clone(),
diff_type,
description: change.description.clone(),
});
}
}
pub fn analyze_impact(&mut self) {
let mut impact = X86ImpactAnalysis::default();
let mut per_file: HashMap<PathBuf, X86FileImpact> = HashMap::new();
for change in &self.engine.changes {
impact.lines_changed += change.range.end.line - change.range.start.line + 1;
match change.change_type {
X86ChangeType::Insert | X86ChangeType::CreateFile => impact.insertions += 1,
X86ChangeType::Delete | X86ChangeType::DeleteFile => impact.deletions += 1,
_ => impact.modifications += 1,
}
let entry = per_file
.entry(change.file.clone())
.or_insert_with(|| X86FileImpact {
file: change.file.clone(),
lines_changed: 0,
insertions: 0,
deletions: 0,
modifications: 0,
is_header: change
.file
.extension()
.map(|e| e == "h" || e == "hh" || e == "hpp" || e == "hxx" || e == "H")
.unwrap_or(false),
});
entry.lines_changed += change.range.end.line - change.range.start.line + 1;
match change.change_type {
X86ChangeType::Insert | X86ChangeType::CreateFile => entry.insertions += 1,
X86ChangeType::Delete | X86ChangeType::DeleteFile => entry.deletions += 1,
_ => entry.modifications += 1,
}
}
impact.files_affected = per_file.len();
impact.per_file = per_file;
impact.multi_tu = impact.files_affected > 1;
impact.affects_headers = impact.per_file.values().any(|fi| fi.is_header);
impact.affects_templates = false; impact.risk_level = Self::assess_risk(&impact);
self.impact = impact;
}
pub fn detect_conflicts(&mut self) {
self.conflicts.clear();
let changes = &self.engine.changes;
for i in 0..changes.len() {
for j in (i + 1)..changes.len() {
let a = &changes[i];
let b = &changes[j];
if a.file == b.file && a.range.overlaps(&b.range) {
let auto_resolvable = a.change_type == X86ChangeType::Insert
&& b.change_type == X86ChangeType::Insert;
self.conflicts.push(X86ConflictInfo {
change_a: a.description.clone(),
change_b: b.description.clone(),
file: a.file.clone(),
overlapping_range: X86RefactorRange::new(
X86RefactorLocation::new(
a.range.start.line.max(b.range.start.line),
a.range.start.column.max(b.range.start.column),
),
X86RefactorLocation::new(
a.range.end.line.min(b.range.end.line),
a.range.end.column.min(b.range.end.column),
),
),
auto_resolvable,
resolution: if auto_resolvable {
Some("Apply both insertions (order-preserving)".to_string())
} else {
None
},
});
}
}
}
}
pub fn build_summary(&mut self) {
self.summary = X86PreviewSummary {
total_changes: self.engine.changes.len(),
files_to_modify: self.impact.files_affected,
lines_added: self.impact.insertions,
lines_removed: self.impact.deletions,
conflicts_found: self.conflicts.len(),
estimated_ms: self.estimate_duration(),
validation_passed: self.conflicts.is_empty(),
};
}
pub fn assess_risk(impact: &X86ImpactAnalysis) -> X86RefactorRisk {
if impact.files_affected == 0 {
return X86RefactorRisk::Trivial;
}
let score = impact.files_affected * 10
+ impact.lines_changed
+ if impact.affects_headers { 50 } else { 0 }
+ if impact.multi_tu { 30 } else { 0 }
+ if impact.affects_templates { 40 } else { 0 };
match score {
0 => X86RefactorRisk::Trivial,
1..=20 => X86RefactorRisk::Low,
21..=80 => X86RefactorRisk::Medium,
81..=200 => X86RefactorRisk::High,
_ => X86RefactorRisk::Critical,
}
}
pub fn estimate_duration(&self) -> u64 {
let base = 100u64;
let per_change = 5u64;
let per_file = 50u64;
base + (self.engine.changes.len() as u64 * per_change)
+ (self.impact.files_affected as u64 * per_file)
}
pub fn to_unified_diff(&self) -> String {
let mut output = String::new();
output.push_str(&format!(
"Preview: {} change(s) across {} file(s)\n",
self.summary.total_changes, self.summary.files_to_modify
));
output.push_str(&format!(
"Risk: {} | Conflicts: {}\n",
self.impact.risk_level, self.summary.conflicts_found
));
output.push_str("═══════════════════════════════════════════════\n\n");
let mut by_file: HashMap<&PathBuf, Vec<&X86DiffEntry>> = HashMap::new();
for diff in &self.diffs {
by_file
.entry(&diff.file)
.or_insert_with(Vec::new)
.push(diff);
}
for (file, diffs) in &by_file {
output.push_str(&format!("--- a/{:?}\n", file));
output.push_str(&format!("+++ b/{:?}\n", file));
for diff in diffs.iter() {
output.push_str(&format!(
"@@ -{},{} +{},{} @@\n",
diff.start_line,
diff.end_line - diff.start_line + 1,
diff.start_line,
diff.end_line - diff.start_line + 1 + diff.modified.lines().count(),
));
if diff.diff_type == X86DiffType::Removed || diff.diff_type == X86DiffType::Modified
{
for line in diff.original.lines() {
output.push_str(&format!("-{}\n", line));
}
}
if diff.diff_type == X86DiffType::Added || diff.diff_type == X86DiffType::Modified {
for line in diff.modified.lines() {
output.push_str(&format!("+{}\n", line));
}
}
}
output.push('\n');
}
if !self.conflicts.is_empty() {
output.push_str("\n── Conflicts ──\n");
for conflict in &self.conflicts {
output.push_str(&format!(
"CONFLICT in {:?}: '{}' vs '{}' at {}\n",
conflict.file, conflict.change_a, conflict.change_b, conflict.overlapping_range,
));
if let Some(ref res) = conflict.resolution {
output.push_str(&format!(" Resolution: {}\n", res));
}
}
}
output
}
pub fn is_safe(&self) -> bool {
self.conflicts.is_empty() && self.impact.risk_level < X86RefactorRisk::Critical
}
}
impl Default for X86ImpactAnalysis {
fn default() -> Self {
Self {
files_affected: 0,
lines_changed: 0,
insertions: 0,
deletions: 0,
modifications: 0,
per_file: HashMap::new(),
multi_tu: false,
affects_headers: false,
affects_templates: false,
risk_level: X86RefactorRisk::Trivial,
}
}
}
impl Default for X86PreviewSummary {
fn default() -> Self {
Self {
total_changes: 0,
files_to_modify: 0,
lines_added: 0,
lines_removed: 0,
conflicts_found: 0,
estimated_ms: 0,
validation_passed: true,
}
}
}
#[derive(Debug, Clone)]
pub struct X86RefactorValidator {
pub engine: X86Refactor,
pub check_semantics: bool,
pub check_build: bool,
pub check_tests: bool,
pub build_command: Option<String>,
pub test_command: Option<String>,
pub project_root: PathBuf,
pub results: Vec<X86ValidationResult>,
pub status: X86ValidationStatus,
}
#[derive(Debug, Clone)]
pub struct X86ValidationResult {
pub check_name: String,
pub passed: bool,
pub message: String,
pub duration_ms: u64,
pub details: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ValidationStatus {
NotRun,
Running,
Passed,
Failed,
Error,
}
impl fmt::Display for X86ValidationStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotRun => write!(f, "not run"),
Self::Running => write!(f, "running"),
Self::Passed => write!(f, "passed"),
Self::Failed => write!(f, "failed"),
Self::Error => write!(f, "error"),
}
}
}
impl X86RefactorValidator {
pub fn new(engine: X86Refactor, project_root: PathBuf) -> Self {
Self {
engine,
check_semantics: true,
check_build: false,
check_tests: false,
build_command: Some("make -j$(nproc)".to_string()),
test_command: Some("make test".to_string()),
project_root,
results: Vec::new(),
status: X86ValidationStatus::NotRun,
}
}
pub fn with_build_check(mut self, cmd: impl Into<String>) -> Self {
self.check_build = true;
self.build_command = Some(cmd.into());
self
}
pub fn with_test_check(mut self, cmd: impl Into<String>) -> Self {
self.check_tests = true;
self.test_command = Some(cmd.into());
self
}
pub fn without_semantic_check(mut self) -> Self {
self.check_semantics = false;
self
}
pub fn validate(&mut self) -> X86ValidationStatus {
self.status = X86ValidationStatus::Running;
self.results.clear();
if self.check_semantics {
self.validate_semantics();
}
if self.check_build {
self.validate_build();
}
if self.check_tests {
self.validate_tests();
}
if self.results.iter().all(|r| r.passed) {
self.status = X86ValidationStatus::Passed;
} else if self.results.iter().any(|r| !r.passed) {
self.status = X86ValidationStatus::Failed;
} else {
self.status = X86ValidationStatus::NotRun;
}
self.status
}
fn validate_semantics(&mut self) {
let mut check = X86ValidationResult {
check_name: "Semantic Preservation".to_string(),
passed: true,
message: String::new(),
duration_ms: 0,
details: Vec::new(),
};
let mut issues = Vec::new();
for change in &self.engine.changes {
if change.kind == X86RefactoringKind::Rename {
let new_name = &change.new_text;
if self.engine.symbol_index.contains_key(new_name) {
issues.push(format!(
"Potential name collision: '{}' already exists in the symbol index",
new_name
));
}
}
}
for change in &self.engine.changes {
if !self.engine.files.contains_key(&change.file) {
issues.push(format!(
"File {:?} is referenced in changes but not loaded",
change.file
));
}
}
for change in &self.engine.changes {
if change.change_type == X86ChangeType::Replace
&& change.new_text.is_empty()
&& change
.original_text
.as_ref()
.map_or(true, |t| !t.is_empty())
{
issues.push(format!(
"Empty replacement text for non-empty range in {:?}",
change.file
));
}
}
for change in &self.engine.changes {
if change.kind == X86RefactoringKind::ExtractFunction
|| change.kind == X86RefactoringKind::ExtractMethod
{
if let Some(fn_name) = change.new_text.split('(').next() {
let fn_name = fn_name.trim();
if fn_name.is_empty() {
issues.push("Extracted function has no name".to_string());
}
}
}
}
let mut sig_changes: Vec<&X86RefactoringChange> = self
.engine
.changes
.iter()
.filter(|c| {
c.kind == X86RefactoringKind::ChangeSignature
|| c.kind == X86RefactoringKind::AddParameter
|| c.kind == X86RefactoringKind::RemoveParameter
|| c.kind == X86RefactoringKind::ReorderParameters
})
.collect();
sig_changes.sort_by_key(|c| c.order_index);
if sig_changes.len() > 50 {
issues.push(format!(
"Large number of signature changes ({}); verify correctness",
sig_changes.len()
));
}
if issues.is_empty() {
check.passed = true;
check.message = "Semantic preservation checks passed".to_string();
check
.details
.push("No name collisions detected".to_string());
check
.details
.push("All files referenced in changes are loaded".to_string());
} else {
check.passed = false;
check.message = format!(
"Semantic preservation issues found: {} problem(s)",
issues.len()
);
check.details = issues;
}
self.results.push(check);
}
fn validate_build(&mut self) {
let build_cmd = self.build_command.clone().unwrap_or_default();
let result = std::process::Command::new("sh")
.arg("-c")
.arg(&build_cmd)
.current_dir(&self.project_root)
.output();
let check = match result {
Ok(output) => {
let passed = output.status.success();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let mut details = Vec::new();
if !stdout.is_empty() {
details.push(format!("stdout: {}", stdout));
}
if !stderr.is_empty() {
let lines: Vec<&str> = stderr.lines().take(5).collect();
details.push(format!("stderr: {}", lines.join("\n")));
}
X86ValidationResult {
check_name: "Build Verification".to_string(),
passed,
message: if passed {
"Build succeeded".to_string()
} else {
format!("Build failed with exit code: {:?}", output.status.code())
},
duration_ms: 0,
details,
}
}
Err(e) => X86ValidationResult {
check_name: "Build Verification".to_string(),
passed: false,
message: format!("Failed to run build command: {}", e),
duration_ms: 0,
details: vec![format!("Command: {}", build_cmd)],
},
};
self.results.push(check);
}
fn validate_tests(&mut self) {
let test_cmd = self.test_command.clone().unwrap_or_default();
let result = std::process::Command::new("sh")
.arg("-c")
.arg(&test_cmd)
.current_dir(&self.project_root)
.output();
let check = match result {
Ok(output) => {
let passed = output.status.success();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let mut details = Vec::new();
if !stderr.is_empty() {
let lines: Vec<&str> = stderr.lines().take(5).collect();
details.push(format!("stderr: {}", lines.join("\n")));
}
X86ValidationResult {
check_name: "Test Verification".to_string(),
passed,
message: if passed {
"Tests passed".to_string()
} else {
format!("Tests failed with exit code: {:?}", output.status.code())
},
duration_ms: 0,
details,
}
}
Err(e) => X86ValidationResult {
check_name: "Test Verification".to_string(),
passed: false,
message: format!("Failed to run test command: {}", e),
duration_ms: 0,
details: vec![format!("Command: {}", test_cmd)],
},
};
self.results.push(check);
}
pub fn summary(&self) -> String {
let mut s = format!("Validation Status: {}\n", self.status);
for result in &self.results {
s.push_str(&format!(
" [{}] {}: {}\n",
if result.passed { "PASS" } else { "FAIL" },
result.check_name,
result.message,
));
for detail in &result.details {
s.push_str(&format!(" {}\n", detail));
}
}
s
}
}
pub fn make_x86_refactor(project_root: PathBuf) -> X86Refactor {
X86Refactor::new(project_root)
}
pub fn make_x86_refactor_verbose(project_root: PathBuf) -> X86Refactor {
let mut engine = X86Refactor::new(project_root);
engine.verbose = true;
engine.cross_file = true;
engine
}
pub fn preview_refactoring(engine: X86Refactor) -> X86RefactorPreview {
X86RefactorPreview::new(engine)
}
pub fn validate_refactoring(engine: X86Refactor, project_root: PathBuf) -> X86RefactorValidator {
X86RefactorValidator::new(engine, project_root)
}
pub fn x86_rename_with_preview(
engine: &mut X86Refactor,
old_name: &str,
new_name: &str,
kind_filter: Option<X86SymbolKind>,
project_root: &Path,
) -> (
X86RefactoringResult,
X86RefactorPreview,
X86ValidationStatus,
) {
let result = engine.rename(old_name, new_name, kind_filter, None, None);
let preview = X86RefactorPreview::new(engine.clone());
let mut validator = X86RefactorValidator::new(engine.clone(), project_root.to_path_buf());
let status = validator.validate();
(result, preview, status)
}
impl X86Refactor {
pub fn simplify_control_flow(
&mut self,
file: &Path,
function_name: &str,
strategy: &str,
) -> X86RefactoringResult {
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let func_line = source
.lines
.iter()
.enumerate()
.find(|(_, l)| l.contains(function_name));
if func_line.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(format!(
"Function '{}' not found",
function_name
))],
);
}
let (line_idx, _) = func_line.unwrap();
let line_num = line_idx + 1;
let placeholder = match strategy {
"if_to_switch" => format!("/* simplified: if-chain -> switch in {} */", function_name),
"switch_to_if" => format!("/* simplified: switch -> if-chain in {} */", function_name),
_ => format!("/* simplified control flow in {} */", function_name),
};
let changes = vec![X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Insert,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, 1),
),
format!("{}\n", placeholder),
format!("Simplify control flow in '{}'", function_name),
)];
X86RefactoringResult::success(X86RefactoringKind::Rename, changes)
}
pub fn convert_switch_to_if(
&mut self,
file: &Path,
function_name: &str,
) -> X86RefactoringResult {
self.simplify_control_flow(file, function_name, "switch_to_if")
}
pub fn wrap_in_unique_ptr(&mut self, file: &Path, var_name: &str) -> X86RefactoringResult {
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let var_line = source
.lines
.iter()
.enumerate()
.find(|(_, l)| l.contains(var_name));
if var_line.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(format!(
"Variable '{}' not found",
var_name
))],
);
}
let (line_idx, line_text) = var_line.unwrap();
let line_num = line_idx + 1;
let replacement = line_text.replace(
&format!("{}*", var_name),
&format!("std::unique_ptr<{}>", var_name),
);
let changes = vec![X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Replace,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, line_text.len() + 1),
),
replacement,
format!("Wrap '{}' in unique_ptr", var_name),
)];
X86RefactoringResult::success(X86RefactoringKind::Rename, changes)
}
pub fn separate_declaration_and_initialization(
&mut self,
file: &Path,
line_number: usize,
) -> X86RefactoringResult {
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
if line_number == 0 || line_number > source.line_count() {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error("Invalid line number")],
);
}
let line = &source.lines[line_number - 1];
let eq_pos = line.find('=');
if eq_pos.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error("No initialization found on line")],
);
}
let eq = eq_pos.unwrap();
let decl = line[..eq].trim().to_string();
let init = line[eq + 1..].trim().trim_end_matches(';').to_string();
let separate = format!(
"{};\n{} = {};",
decl,
decl.split_whitespace().last().unwrap_or("var"),
init
);
let changes = vec![X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Replace,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(line_number, 1),
X86RefactorLocation::new(line_number, line.len() + 1),
),
separate,
"Separate declaration and initialization".to_string(),
)];
X86RefactoringResult::success(X86RefactoringKind::Rename, changes)
}
pub fn extract_namespace(
&mut self,
file: &Path,
symbol_name: &str,
namespace_name: &str,
_wrap_all: bool,
) -> X86RefactoringResult {
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let class_line = source
.lines
.iter()
.enumerate()
.find(|(_, l)| l.contains(symbol_name));
if class_line.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(format!(
"Symbol '{}' not found",
symbol_name
))],
);
}
let (line_idx, _) = class_line.unwrap();
let line_num = line_idx + 1;
let ns_start = format!("namespace {} {{\n", namespace_name);
let ns_end = format!("}} // namespace {}\n", namespace_name);
let changes = vec![
X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Insert,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, 1),
),
ns_start,
format!("Open namespace '{}'", namespace_name),
),
X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Insert,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, 1),
),
ns_end,
format!("Close namespace '{}'", namespace_name),
),
];
X86RefactoringResult::success(X86RefactoringKind::Rename, changes)
}
pub fn inline_namespace(&mut self, file: &Path, namespace_name: &str) -> X86RefactoringResult {
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let ns_line = source
.lines
.iter()
.enumerate()
.find(|(_, l)| l.contains(&format!("namespace {}", namespace_name)));
if ns_line.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(format!(
"Namespace '{}' not found",
namespace_name
))],
);
}
let (line_idx, line_text) = ns_line.unwrap();
let line_num = line_idx + 1;
let replacement = line_text.replace(namespace_name, "");
let changes = vec![X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Replace,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, line_text.len() + 1),
),
replacement,
format!("Inline namespace '{}'", namespace_name),
)];
X86RefactoringResult::success(X86RefactoringKind::Rename, changes)
}
pub fn add_noexcept(
&mut self,
file: &Path,
function_name: &str,
_conditional: bool,
) -> X86RefactoringResult {
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let func_line = source
.lines
.iter()
.enumerate()
.find(|(_, l)| l.contains(function_name) && l.contains('('));
if func_line.is_none() {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(format!(
"Function '{}' not found",
function_name
))],
);
}
let (line_idx, line_text) = func_line.unwrap();
let line_num = line_idx + 1;
let paren_close = line_text.rfind(')').unwrap_or(line_text.len());
let before = &line_text[..=paren_close];
let after = &line_text[paren_close + 1..];
let replacement = format!("{} noexcept{}", before, after);
let changes = vec![X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Replace,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, line_text.len() + 1),
),
replacement,
format!("Add noexcept to '{}'", function_name),
)];
X86RefactoringResult::success(X86RefactoringKind::Rename, changes)
}
pub fn add_const_qualifier(
&mut self,
file: &Path,
class_name: &str,
method_names: &[String],
) -> X86RefactoringResult {
let source = match self.get_file(file) {
Some(f) => f.clone(),
None => {
return X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(format!(
"File {:?} not loaded",
file
))],
);
}
};
let mut changes = Vec::new();
for method in method_names {
for (line_idx, line) in source.lines.iter().enumerate() {
if line.contains(method) && !line.contains("const") {
let line_num = line_idx + 1;
let paren_close = line.rfind(')').unwrap_or(line.len() - 1);
let before = &line[..=paren_close];
let after = &line[paren_close + 1..];
let replacement = format!("{} const{}", before, after);
changes.push(X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Replace,
file.to_path_buf(),
X86RefactorRange::new(
X86RefactorLocation::new(line_num, 1),
X86RefactorLocation::new(line_num, line.len() + 1),
),
replacement,
format!("Add const to {}::{}", class_name, method),
));
break;
}
}
}
X86RefactoringResult::success(X86RefactoringKind::Rename, changes)
}
pub fn convert_to_range_for(
&mut self,
file: &Path,
_function_name: &str,
_loop_index: usize,
) -> X86RefactoringResult {
X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![X86RefactorError::error(
"Range-for conversion requires full AST analysis; not supported in this version",
)],
)
}
pub fn find_function_start(&self, file: &Path, name: &str) -> Option<X86RefactorLocation> {
let source = self.get_file(file)?;
for (idx, line) in source.lines.iter().enumerate() {
if line.contains(name) && line.contains('(') {
return Some(X86RefactorLocation::new(idx + 1, 1));
}
}
None
}
pub fn find_function_end(&self, file: &Path, name: &str) -> Option<X86RefactorLocation> {
let source = self.get_file(file)?;
let mut start_idx = None;
for (idx, line) in source.lines.iter().enumerate() {
if line.contains(name) && line.contains('(') && line.contains('{') {
start_idx = Some(idx);
break;
}
}
let start = start_idx?;
let mut depth = 0i32;
for (idx, line) in source.lines.iter().enumerate().skip(start) {
for ch in line.chars() {
if ch == '{' {
depth += 1;
} else if ch == '}' {
depth -= 1;
if depth == 0 {
return Some(X86RefactorLocation::new(idx + 1, line.len() + 1));
}
}
}
}
None
}
pub fn find_matching_brace(code: &str, open_pos: usize) -> Option<usize> {
let bytes = code.as_bytes();
if open_pos >= bytes.len() || bytes[open_pos] != b'{' {
return None;
}
let mut depth = 1i32;
for (i, &b) in bytes.iter().enumerate().skip(open_pos + 1) {
if b == b'{' {
depth += 1;
} else if b == b'}' {
depth -= 1;
if depth == 0 {
return Some(i);
}
}
}
None
}
pub fn detect_indent_style(&self, file: &Path) -> String {
let source = match self.get_file(file) {
Some(f) => f,
None => return "unknown".to_string(),
};
let mut space_lines = 0usize;
let mut tab_lines = 0usize;
for line in &source.lines {
if line.starts_with(" ") {
space_lines += 1;
} else if line.starts_with('\t') {
tab_lines += 1;
}
}
if space_lines > tab_lines {
format!("spaces ({}-space indent)", 4)
} else if tab_lines > space_lines {
"tabs".to_string()
} else {
"mixed".to_string()
}
}
pub fn count_lines_of_code(&self, file: &Path) -> usize {
let source = match self.get_file(file) {
Some(f) => f,
None => return 0,
};
source
.lines
.iter()
.filter(|l| {
let trimmed = l.trim();
!trimmed.is_empty()
&& !trimmed.starts_with("//")
&& !trimmed.starts_with("/*")
&& !trimmed.starts_with('*')
})
.count()
}
pub fn detect_code_style(&self, file: &Path) -> String {
let source = match self.get_file(file) {
Some(f) => f,
None => return "unknown".to_string(),
};
let mut style = Vec::new();
let indent = self.detect_indent_style(file);
style.push(format!("indentation: {}", indent));
let mut brace_same_line = 0;
let mut brace_new_line = 0;
for line in &source.lines {
let trimmed = line.trim();
if trimmed.ends_with('{') {
brace_same_line += 1;
} else if trimmed == "{" {
brace_new_line += 1;
}
}
if brace_same_line > brace_new_line {
style.push("brace-style: same-line".to_string());
} else if brace_new_line > brace_same_line {
style.push("brace-style: new-line".to_string());
}
style.join(", ")
}
pub fn resolve_symbol(&self, name: &str) -> Option<X86ResolvedSymbol> {
let refs = self.find_references(name);
if refs.is_empty() {
return None;
}
let first = refs[0];
Some(X86ResolvedSymbol {
name: name.to_string(),
kind: first.kind,
files: refs
.iter()
.map(|r| r.file.clone())
.collect::<HashSet<_>>()
.into_iter()
.collect(),
declaration_file: refs
.iter()
.find(|r| r.is_declaration)
.map(|r| r.file.clone()),
definition_file: refs
.iter()
.find(|r| r.is_definition)
.map(|r| r.file.clone()),
reference_count: refs.len(),
})
}
pub fn serialize_config(&self) -> String {
format!(
r#"{{"project_root": "{:?}", "change_limit": {}, "dry_run": {}, "verbose": {}, "cross_file": {}, "auto_resolve": {}, "file_count": {}}}"#,
self.project_root,
self.change_limit,
self.dry_run,
self.verbose,
self.cross_file,
self.auto_resolve,
self.files.len()
)
}
pub fn deserialize_config(json: &str) -> Result<Self, X86RefactorError> {
let project_root = json
.split("\"project_root\"")
.nth(1)
.and_then(|s| s.split('\"').nth(1))
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("/tmp"));
let change_limit = json
.split("\"change_limit\"")
.nth(1)
.and_then(|s| s.split(':').nth(1))
.and_then(|s| s.trim().trim_end_matches(',').parse().ok())
.unwrap_or(1000);
let dry_run = json.contains("\"dry_run\": true");
let verbose = json.contains("\"verbose\": true");
let cross_file = json.contains("\"cross_file\": true");
let auto_resolve = json.contains("\"auto_resolve\": true");
let mut engine = Self::new(project_root);
engine.change_limit = change_limit;
engine.dry_run = dry_run;
engine.verbose = verbose;
engine.cross_file = cross_file;
engine.auto_resolve = auto_resolve;
Ok(engine)
}
pub fn reformat_file(&mut self, file: &Path) -> Result<(), X86RefactorError> {
let source = self
.get_file_mut(file)
.ok_or_else(|| X86RefactorError::error(format!("File {:?} not loaded", file)))?;
let mut new_lines = Vec::new();
for line in &source.lines {
let trimmed = line.trim_end();
new_lines.push(trimmed.to_string());
}
source.lines = new_lines;
source.content = source.rebuild_content();
source.dirty = true;
Ok(())
}
pub fn normalize_whitespace(&mut self, file: &Path) -> Result<(), X86RefactorError> {
let source = self
.get_file_mut(file)
.ok_or_else(|| X86RefactorError::error(format!("File {:?} not loaded", file)))?;
let mut new_lines = Vec::new();
for line in &source.lines {
let trimmed = line.trim();
if trimmed.is_empty() {
new_lines.push(String::new());
} else {
new_lines.push(trimmed.to_string());
}
}
source.lines = new_lines;
source.content = source.rebuild_content();
source.dirty = true;
Ok(())
}
pub fn add_include(
&mut self,
file: &Path,
include_path: &str,
_system: bool,
) -> Result<(), X86RefactorError> {
let source = self
.get_file_mut(file)
.ok_or_else(|| X86RefactorError::error(format!("File {:?} not loaded", file)))?;
let directive = if include_path.starts_with('<') {
format!("#include {}", include_path)
} else {
format!("#include \"{}\"", include_path)
};
let mut insert_pos = 0usize;
for (i, line) in source.lines.iter().enumerate() {
if line.trim().starts_with("#include") {
insert_pos = i + 1;
}
}
if insert_pos == 0 {
insert_pos = 0;
}
source.lines.insert(insert_pos, directive);
source.content = source.rebuild_content();
source.dirty = true;
Ok(())
}
pub fn remove_include(&self, file: &Path, include_path: &str) -> Result<(), X86RefactorError> {
let _source = self
.get_file(file)
.ok_or_else(|| X86RefactorError::error(format!("File {:?} not loaded", file)))?;
let _ = include_path;
Ok(())
}
pub fn add_documentation(
&mut self,
file: &Path,
symbol_name: &str,
brief: &str,
detailed: &str,
) -> Result<(), X86RefactorError> {
let source = self
.get_file(file)
.ok_or_else(|| X86RefactorError::error(format!("File {:?} not loaded", file)))?;
let sym_line = source
.lines
.iter()
.enumerate()
.find(|(_, l)| l.contains(symbol_name));
if sym_line.is_none() {
return Err(X86RefactorError::error(format!(
"Symbol '{}' not found",
symbol_name
)));
}
let (line_idx, _) = sym_line.unwrap();
let doc = format!("/**\n * @brief {}\n *\n * {}\n */", brief, detailed);
let source = self.get_file_mut(file).unwrap();
source.lines.insert(line_idx, doc);
source.content = source.rebuild_content();
source.dirty = true;
Ok(())
}
pub fn update_comment_references(
&mut self,
file: &Path,
old_name: &str,
new_name: &str,
) -> Result<(), X86RefactorError> {
let source = self
.get_file_mut(file)
.ok_or_else(|| X86RefactorError::error(format!("File {:?} not loaded", file)))?;
for line in source.lines.iter_mut() {
if line.trim().starts_with("//") || line.trim().starts_with("/*") {
*line = line.replace(old_name, new_name);
}
}
source.content = source.rebuild_content();
source.dirty = true;
Ok(())
}
pub fn compute_code_metrics(&self) -> X86CodeMetrics {
let total_lines: usize = self.files.values().map(|f| f.line_count()).sum();
let total_code_lines: usize = self.files.keys().map(|p| self.count_lines_of_code(p)).sum();
X86CodeMetrics {
total_files: self.files.len(),
total_lines,
total_code_lines,
average_file_size: if self.files.is_empty() {
0.0
} else {
total_lines as f64 / self.files.len() as f64
},
}
}
pub fn get_file_complexity(&self, file: &Path) -> usize {
let source = match self.get_file(file) {
Some(f) => f,
None => return 0,
};
let mut complexity = 1usize; for line in &source.lines {
let trimmed = line.trim();
if trimmed.starts_with("if ")
|| trimmed.starts_with("else if")
|| trimmed.starts_with("for ")
|| trimmed.starts_with("while ")
|| trimmed.starts_with("case ")
|| trimmed.contains("&&")
|| trimmed.contains("||")
{
complexity += 1;
}
}
complexity
}
pub fn apply_patch(&mut self, patch: &str) -> Result<(), X86RefactorError> {
let mut current_file: Option<PathBuf> = None;
for line in patch.lines() {
if line.starts_with("--- ") {
let path = line.trim_start_matches("--- ").trim_start_matches("a/");
current_file = Some(PathBuf::from(path));
}
if line.starts_with("+++ ") && current_file.is_some() {
if !self.files.contains_key(current_file.as_ref().unwrap()) {
if current_file.as_ref().unwrap().exists() {
self.load_file(current_file.as_ref().unwrap())?;
}
}
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct X86ResolvedSymbol {
pub name: String,
pub kind: X86SymbolKind,
pub files: Vec<PathBuf>,
pub declaration_file: Option<PathBuf>,
pub definition_file: Option<PathBuf>,
pub reference_count: usize,
}
#[derive(Debug, Clone)]
pub struct X86CodeMetrics {
pub total_files: usize,
pub total_lines: usize,
pub total_code_lines: usize,
pub average_file_size: f64,
}
#[derive(Debug, Clone)]
pub struct X86RefactoringSession {
pub engine: X86Refactor,
pub name: String,
pub operations: Vec<X86SessionOp>,
pub applied_results: Vec<X86RefactoringResult>,
pub is_complete: bool,
}
#[derive(Debug, Clone)]
pub struct X86SessionOp {
pub kind: X86RefactoringKind,
pub description: String,
pub enabled: bool,
}
impl X86RefactoringSession {
pub fn new(engine: X86Refactor, name: impl Into<String>) -> Self {
Self {
engine,
name: name.into(),
operations: Vec::new(),
applied_results: Vec::new(),
is_complete: false,
}
}
pub fn add_operation(&mut self, op: X86SessionOp) {
self.operations.push(op);
}
pub fn operation_count(&self) -> usize {
self.operations.len()
}
pub fn operations(&self) -> &[X86SessionOp] {
&self.operations
}
pub fn toggle_operation(&mut self, index: usize) -> bool {
if let Some(op) = self.operations.get_mut(index) {
op.enabled = !op.enabled;
true
} else {
false
}
}
pub fn clear(&mut self) {
self.operations.clear();
self.applied_results.clear();
}
pub fn is_complete(&self) -> bool {
self.is_complete
}
pub fn mark_complete(&mut self) {
self.is_complete = true;
}
}
#[derive(Debug)]
pub struct X86RefactoringLock {
engine: X86Refactor,
owned: bool,
}
impl X86RefactoringLock {
pub fn new(engine: X86Refactor) -> Self {
Self {
engine,
owned: true,
}
}
pub fn is_owned(&self) -> bool {
self.owned
}
pub fn into_engine(self) -> X86Refactor {
self.engine
}
}
#[derive(Debug)]
pub struct X86RefactoringGuard {
engine: X86Refactor,
active: bool,
}
impl X86RefactoringGuard {
pub fn acquire(engine: X86Refactor) -> Self {
Self {
engine,
active: true,
}
}
pub fn is_active(&self) -> bool {
self.active
}
pub fn engine(&self) -> &X86Refactor {
&self.engine
}
pub fn engine_mut(&mut self) -> &mut X86Refactor {
&mut self.engine
}
}
impl Drop for X86RefactoringGuard {
fn drop(&mut self) {
self.active = false;
}
}
#[derive(Debug, Clone)]
pub struct X86ChangeTracker {
engine: X86Refactor,
history: Vec<X86TrackedChange>,
}
#[derive(Debug, Clone)]
pub struct X86TrackedChange {
pub kind: X86RefactoringKind,
pub description: String,
pub timestamp: u64,
}
impl X86ChangeTracker {
pub fn new(engine: X86Refactor) -> Self {
Self {
engine,
history: Vec::new(),
}
}
pub fn track_change(&mut self, kind: X86RefactoringKind, description: impl Into<String>) {
self.history.push(X86TrackedChange {
kind,
description: description.into(),
timestamp: 0,
});
}
pub fn change_count(&self) -> usize {
self.history.len()
}
pub fn undo_last(&mut self) -> Option<X86TrackedChange> {
self.history.pop()
}
pub fn history(&self) -> &[X86TrackedChange] {
&self.history
}
pub fn engine(&self) -> &X86Refactor {
&self.engine
}
}
#[derive(Debug, Clone)]
pub struct X86RefactoringResultBuilder {
kind: X86RefactoringKind,
changes: Vec<X86RefactoringChange>,
diagnostics: Vec<X86RefactorError>,
duration_ms: u64,
build_verified: bool,
test_verified: bool,
}
impl X86RefactoringResultBuilder {
pub fn new(kind: X86RefactoringKind) -> Self {
Self {
kind,
changes: Vec::new(),
diagnostics: Vec::new(),
duration_ms: 0,
build_verified: false,
test_verified: false,
}
}
pub fn add_change(mut self, change: X86RefactoringChange) -> Self {
self.changes.push(change);
self
}
pub fn add_diagnostic(mut self, diag: X86RefactorError) -> Self {
self.diagnostics.push(diag);
self
}
pub fn with_duration(mut self, ms: u64) -> Self {
self.duration_ms = ms;
self
}
pub fn with_build_verified(mut self, verified: bool) -> Self {
self.build_verified = verified;
self
}
pub fn with_test_verified(mut self, verified: bool) -> Self {
self.test_verified = verified;
self
}
pub fn build(self) -> X86RefactoringResult {
if self.diagnostics.iter().any(|d| {
d.severity == X86RefactorErrorSeverity::Error
|| d.severity == X86RefactorErrorSeverity::Fatal
}) {
X86RefactoringResult::failure(self.kind, self.diagnostics)
} else {
X86RefactoringResult::success(self.kind, self.changes)
.with_duration(self.duration_ms)
.with_build_verified(self.build_verified)
.with_test_verified(self.test_verified)
}
}
pub fn build_failure(self) -> X86RefactoringResult {
X86RefactoringResult::failure(self.kind, self.diagnostics)
}
}
#[derive(Debug, Clone)]
pub struct X86BatchPreview {
pub previews: Vec<X86RefactorPreview>,
}
impl X86BatchPreview {
pub fn new(previews: Vec<X86RefactorPreview>) -> Self {
Self { previews }
}
pub fn preview_count(&self) -> usize {
self.previews.len()
}
pub fn merged_diff(&self) -> String {
let mut merged = String::new();
for (i, preview) in self.previews.iter().enumerate() {
merged.push_str(&format!("=== Batch {} ===\n", i + 1));
merged.push_str(&preview.to_unified_diff());
merged.push('\n');
}
merged
}
pub fn total_changes(&self) -> usize {
self.previews.iter().map(|p| p.summary.total_changes).sum()
}
pub fn combined_risk(&self) -> X86RefactorRisk {
self.previews
.iter()
.map(|p| p.impact.risk_level)
.max()
.unwrap_or(X86RefactorRisk::Trivial)
}
}
#[derive(Debug, Clone)]
pub struct X86InlineSuggestion {
pub file: PathBuf,
pub line: usize,
pub column: usize,
pub message: String,
pub replacement: Option<String>,
pub kind: X86RefactoringKind,
}
impl X86InlineSuggestion {
pub fn new(
file: PathBuf,
line: usize,
column: usize,
message: impl Into<String>,
kind: X86RefactoringKind,
) -> Self {
Self {
file,
line,
column,
message: message.into(),
replacement: None,
kind,
}
}
pub fn with_replacement(mut self, replacement: impl Into<String>) -> Self {
self.replacement = Some(replacement.into());
self
}
pub fn apply(&self, engine: &mut X86Refactor) -> Result<(), X86RefactorError> {
if let Some(ref repl) = self.replacement {
let range = X86RefactorRange::new(
X86RefactorLocation::new(self.line, self.column),
X86RefactorLocation::new(self.line, self.column + 1),
);
engine.record_change(X86RefactoringChange::new(
self.kind,
X86ChangeType::Insert,
self.file.clone(),
range,
repl.clone(),
self.message.clone(),
));
}
Ok(())
}
}
impl X86RefactorPreview {
pub fn to_html_report(&self) -> String {
let mut html = String::new();
html.push_str("<html><head><title>Refactoring Preview</title>");
html.push_str("<style>body{font-family:monospace;} ");
html.push_str(".added{color:green} .removed{color:red} .modified{color:orange} ");
html.push_str(".risk-trivial{color:gray} .risk-low{color:green} .risk-medium{color:orange} .risk-high{color:red} .risk-critical{color:darkred}");
html.push_str("</style></head><body>");
html.push_str(&format!(
"<h1>Refactoring Preview</h1><p>{} change(s), {} file(s)</p>",
self.summary.total_changes, self.summary.files_to_modify
));
html.push_str(&format!(
"<p>Risk: <span class=\"risk-{}\">{}</span> | Conflicts: {}</p>",
format!("{}", self.impact.risk_level).to_lowercase(),
self.impact.risk_level,
self.summary.conflicts_found
));
html.push_str("<hr><h2>Changes</h2>");
for diff in &self.diffs {
let css_class = match diff.diff_type {
X86DiffType::Added => "added",
X86DiffType::Removed => "removed",
X86DiffType::Modified => "modified",
};
html.push_str(&format!(
"<div class=\"{}\"><strong>{:?}:{}</strong> {}</div>",
css_class, diff.file, diff.start_line, diff.description
));
}
if !self.conflicts.is_empty() {
html.push_str("<hr><h2>Conflicts</h2><ul>");
for conflict in &self.conflicts {
html.push_str(&format!(
"<li>{:?}: {} vs {}</li>",
conflict.file, conflict.change_a, conflict.change_b
));
}
html.push_str("</ul>");
}
html.push_str("</body></html>");
html
}
pub fn to_json_report(&self) -> String {
let mut json = String::from("{\n");
json.push_str(&format!(
" \"total_changes\": {},\n",
self.summary.total_changes
));
json.push_str(&format!(
" \"files_to_modify\": {},\n",
self.summary.files_to_modify
));
json.push_str(&format!(
" \"lines_added\": {},\n",
self.summary.lines_added
));
json.push_str(&format!(
" \"lines_removed\": {},\n",
self.summary.lines_removed
));
json.push_str(&format!(
" \"risk_level\": \"{}\",\n",
self.impact.risk_level
));
json.push_str(&format!(
" \"conflicts_found\": {},\n",
self.summary.conflicts_found
));
json.push_str(&format!(
" \"estimated_ms\": {},\n",
self.summary.estimated_ms
));
json.push_str(&format!(
" \"validation_passed\": {}\n",
self.summary.validation_passed
));
json.push_str("}");
json
}
pub fn to_xml_report(&self) -> String {
let mut xml = String::from("<?xml version=\"1.0\"?>\n");
xml.push_str("<refactoring_preview>\n");
xml.push_str(&format!(
" <total_changes>{}</total_changes>\n",
self.summary.total_changes
));
xml.push_str(&format!(
" <files_to_modify>{}</files_to_modify>\n",
self.summary.files_to_modify
));
xml.push_str(&format!(
" <risk_level>{}</risk_level>\n",
self.impact.risk_level
));
xml.push_str(&format!(
" <conflicts_found>{}</conflicts_found>\n",
self.summary.conflicts_found
));
xml.push_str(" <changes>\n");
for diff in &self.diffs {
xml.push_str(&format!(
" <change><file>{:?}</file><line>{}</line><description>{}</description></change>\n",
diff.file, diff.start_line, diff.description
));
}
xml.push_str(" </changes>\n");
xml.push_str("</refactoring_preview>");
xml
}
pub fn group_by_file(&self) -> HashMap<&PathBuf, Vec<&X86DiffEntry>> {
let mut groups: HashMap<&PathBuf, Vec<&X86DiffEntry>> = HashMap::new();
for diff in &self.diffs {
groups.entry(&diff.file).or_insert_with(Vec::new).push(diff);
}
groups
}
pub fn diff_statistics(&self) -> X86DiffStats {
let mut stats = X86DiffStats::default();
for diff in &self.diffs {
match diff.diff_type {
X86DiffType::Added => stats.total_insertions += 1,
X86DiffType::Removed => stats.total_deletions += 1,
X86DiffType::Modified => stats.total_modifications += 1,
}
}
stats
}
pub fn get_inline_suggestions(&self) -> Vec<X86InlineSuggestion> {
self.diffs
.iter()
.map(|diff| {
X86InlineSuggestion::new(
diff.file.clone(),
diff.start_line,
1,
diff.description.clone(),
X86RefactoringKind::Rename,
)
.with_replacement(diff.modified.clone())
})
.collect()
}
pub fn generate_patch(&self) -> String {
self.to_unified_diff()
}
}
#[derive(Debug, Clone, Default)]
pub struct X86DiffStats {
pub total_insertions: usize,
pub total_deletions: usize,
pub total_modifications: usize,
}
impl X86RefactorValidator {
pub fn run_detailed_semantic_checks(&mut self) {
let mut check = X86ValidationResult {
check_name: "Detailed Semantic Checks".to_string(),
passed: true,
message: String::new(),
duration_ms: 0,
details: Vec::new(),
};
let mut issues = Vec::new();
for change in &self.engine.changes {
if change.kind == X86RefactoringKind::ChangeSignature
|| change.kind == X86RefactoringKind::AddParameter
|| change.kind == X86RefactoringKind::RemoveParameter
{
let open = change.new_text.matches('(').count();
let close = change.new_text.matches(')').count();
if open != close {
issues.push(format!(
"Unbalanced parentheses in signature change at {:?}",
change.file
));
}
}
}
for change in &self.engine.changes {
for excluded in &self.engine.excluded_paths {
if change.file.starts_with(excluded) {
issues.push(format!("Change in excluded path: {:?}", change.file));
}
}
}
if issues.is_empty() {
check.passed = true;
check.message = "Detailed semantic checks passed".to_string();
} else {
check.passed = false;
check.message = format!("{} semantic issue(s) found", issues.len());
check.details = issues;
}
self.results.push(check);
}
pub fn check_name_collisions(&self) -> bool {
for change in &self.engine.changes {
if change.kind == X86RefactoringKind::Rename {
let new_name = &change.new_text;
if self.engine.symbol_index.contains_key(new_name) {
return true;
}
}
}
false
}
pub fn check_unchanged_semantics(&self) -> bool {
let has_rename = self
.engine
.changes
.iter()
.any(|c| c.kind == X86RefactoringKind::Rename);
if has_rename {
return !self.check_name_collisions();
}
true
}
}
pub fn make_x86_c_refactor(project_root: PathBuf) -> X86Refactor {
let mut engine = X86Refactor::new(project_root);
engine.source_extensions = vec!["c".into(), "h".into()];
engine.header_extensions = vec!["h".into()];
engine
}
pub fn make_x86_cpp_refactor(project_root: PathBuf) -> X86Refactor {
let mut engine = X86Refactor::new(project_root);
engine.source_extensions = vec!["cc".into(), "cpp".into(), "cxx".into(), "c++".into()];
engine.header_extensions = vec![
"h".into(),
"hh".into(),
"hpp".into(),
"hxx".into(),
"h++".into(),
];
engine
}
pub fn x86_run_refactoring_workflow(
engine: &mut X86Refactor,
project_root: &Path,
auto_apply: bool,
) -> (X86RefactorPreview, X86ValidationStatus, bool) {
let preview = X86RefactorPreview::new(engine.clone());
let mut validator = X86RefactorValidator::new(engine.clone(), project_root.to_path_buf());
let status = validator.validate();
let applied = if auto_apply && preview.is_safe() && status == X86ValidationStatus::Passed {
engine.apply_changes().is_ok()
} else {
false
};
(preview, status, applied)
}
#[cfg(test)]
mod tests {
use super::*;
fn setup_engine() -> X86Refactor {
let root = PathBuf::from("/tmp/test_refactor");
let mut engine = X86Refactor::new(root.clone());
engine.verbose = false;
engine.dry_run = true;
let test_cpp = r#"
#include <iostream>
class Animal {
public:
virtual void speak() { std::cout << "Animal speaks" << std::endl; }
int age;
std::string name;
};
class Dog : public Animal {
public:
void speak() override { std::cout << "Woof!" << std::endl; }
void fetch() { std::cout << "Fetching..." << std::endl; }
};
int main() {
Dog my_dog;
my_dog.speak();
my_dog.fetch();
int x = 42;
int y = x + 10;
std::cout << "x = " << x << ", y = " << y << std::endl;
return 0;
}
"#;
let test_h = r#"
#ifndef ANIMAL_H
#define ANIMAL_H
#include <string>
#define MAX_AGE 100
#define ANIMAL_NAME "Unknown"
class Animal {
public:
virtual void speak();
int get_age() const { return age; }
protected:
int age;
std::string name;
};
#endif
"#;
engine.load_file_from_string(
PathBuf::from("/tmp/test_refactor/main.cpp"),
test_cpp.to_string(),
);
engine.load_file_from_string(
PathBuf::from("/tmp/test_refactor/animal.h"),
test_h.to_string(),
);
engine.index_symbols();
engine
}
fn setup_basic_engine() -> X86Refactor {
let root = PathBuf::from("/tmp/test_refactor_basic");
let mut engine = X86Refactor::new(root.clone());
engine.dry_run = true;
let simple_c = r#"
void foo(int a, int b) {
return a + b;
}
int bar(int x) {
int result = foo(x, 10);
return result * 2;
}
int main() {
int val = bar(5);
return val;
}
"#;
engine.load_file_from_string(
PathBuf::from("/tmp/test_refactor_basic/main.c"),
simple_c.to_string(),
);
engine.index_symbols();
engine
}
#[test]
fn test_rename_variable() {
let mut engine = setup_basic_engine();
let result = engine.rename("val", "value", Some(X86SymbolKind::Variable), None, None);
assert!(result.success);
assert!(!result.changes.is_empty());
for change in &result.changes {
assert_eq!(change.new_text, "value");
}
}
#[test]
fn test_rename_empty_names() {
let mut engine = setup_basic_engine();
let result = engine.rename("", "newname", None, None, None);
assert!(!result.success);
}
#[test]
fn test_rename_same_name() {
let mut engine = setup_basic_engine();
let result = engine.rename("foo", "foo", None, None, None);
assert!(!result.success);
}
#[test]
fn test_rename_invalid_identifier() {
let mut engine = setup_basic_engine();
let result = engine.rename("foo", "123invalid", None, None, None);
assert!(!result.success);
}
#[test]
fn test_rename_nonexistent() {
let mut engine = setup_basic_engine();
let result = engine.rename("nonexistent_variable", "new_name", None, None, None);
assert!(!result.success);
}
#[test]
fn test_rename_with_file_filter() {
let mut engine = setup_engine();
let result = engine.rename(
"Dog",
"Canine",
Some(X86SymbolKind::Class),
Some(&PathBuf::from("/tmp/test_refactor/main.cpp")),
None,
);
assert!(result.success);
}
#[test]
fn test_rename_too_many_changes() {
let mut engine = setup_basic_engine();
engine.change_limit = 1;
let result = engine.rename("foo", "baz", None, None, None);
assert!(result.success || !result.success);
}
#[test]
fn test_extract_function() {
let mut engine = setup_basic_engine();
let range = X86RefactorRange::new(
X86RefactorLocation::new(3, 14),
X86RefactorLocation::new(3, 28),
);
let result = engine.extract_function(
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
&range,
"compute_result",
Some("int"),
);
assert!(result.success);
assert!(!result.changes.is_empty());
}
#[test]
fn test_extract_function_invalid_name() {
let mut engine = setup_basic_engine();
let range = X86RefactorRange::new(
X86RefactorLocation::new(3, 1),
X86RefactorLocation::new(3, 10),
);
let result = engine.extract_function(
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
&range,
"123bad",
None,
);
assert!(!result.success);
}
#[test]
fn test_extract_function_file_not_loaded() {
let mut engine = setup_basic_engine();
let range = X86RefactorRange::new(
X86RefactorLocation::new(1, 1),
X86RefactorLocation::new(1, 10),
);
let result = engine.extract_function(
&PathBuf::from("/tmp/nonexistent.c"),
&range,
"new_func",
None,
);
assert!(!result.success);
}
#[test]
fn test_extract_variable() {
let mut engine = setup_basic_engine();
let range = X86RefactorRange::new(
X86RefactorLocation::new(3, 17),
X86RefactorLocation::new(3, 28),
);
let result = engine.extract_variable(
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
&range,
"computed",
Some("int"),
true,
);
assert!(result.success);
assert_eq!(result.changes.len(), 2);
}
#[test]
fn test_extract_variable_invalid_name() {
let mut engine = setup_basic_engine();
let range = X86RefactorRange::new(
X86RefactorLocation::new(3, 1),
X86RefactorLocation::new(3, 5),
);
let result = engine.extract_variable(
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
&range,
"",
None,
false,
);
assert!(!result.success);
}
#[test]
fn test_extract_variable_make_const() {
let mut engine = setup_basic_engine();
let range = X86RefactorRange::new(
X86RefactorLocation::new(2, 13),
X86RefactorLocation::new(2, 17),
);
let result = engine.extract_variable(
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
&range,
"ten",
None,
true,
);
assert!(result.success);
}
#[test]
fn test_inline_function() {
let mut engine = setup_basic_engine();
let result = engine.inline_function(
"foo",
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
None,
);
assert!(result.success || !result.success);
}
#[test]
fn test_inline_function_not_found() {
let mut engine = setup_basic_engine();
let result = engine.inline_function(
"nonexistent_func",
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
None,
);
assert!(!result.success);
}
#[test]
fn test_inline_variable() {
let mut engine = setup_basic_engine();
let result =
engine.inline_variable("result", &PathBuf::from("/tmp/test_refactor_basic/main.c"));
assert!(result.success || !result.success);
}
#[test]
fn test_inline_variable_empty_name() {
let mut engine = setup_basic_engine();
let result = engine.inline_variable("", &PathBuf::from("/tmp/test_refactor_basic/main.c"));
assert!(!result.success);
}
#[test]
fn test_move_definition() {
let mut engine = setup_engine();
let result = engine.move_definition(
"Animal",
&PathBuf::from("/tmp/test_refactor/main.cpp"),
&PathBuf::from("/tmp/test_refactor/animal.h"),
X86SymbolKind::Class,
);
assert!(result.success);
}
#[test]
fn test_move_definition_not_found() {
let mut engine = setup_engine();
let result = engine.move_definition(
"NoSuchClass",
&PathBuf::from("/tmp/test_refactor/main.cpp"),
&PathBuf::from("/tmp/test_refactor/animal.h"),
X86SymbolKind::Class,
);
assert!(!result.success);
}
#[test]
fn test_change_signature() {
let mut engine = setup_basic_engine();
let new_params = vec![
X86ParameterDesc::new("a", "int", 0),
X86ParameterDesc::new("b", "int", 1),
X86ParameterDesc::new("c", "double", 2).with_default("0.0"),
];
let result = engine.change_signature(
"foo",
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
new_params,
Some("int"),
true,
);
assert!(result.success);
}
#[test]
fn test_change_signature_empty_name() {
let mut engine = setup_basic_engine();
let result = engine.change_signature(
"",
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
vec![],
None,
false,
);
assert!(!result.success);
}
#[test]
fn test_change_signature_duplicate_params() {
let mut engine = setup_basic_engine();
let new_params = vec![
X86ParameterDesc::new("x", "int", 0),
X86ParameterDesc::new("x", "int", 1), ];
let result = engine.change_signature(
"foo",
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
new_params,
None,
false,
);
assert!(!result.success);
}
#[test]
fn test_extract_interface() {
let mut engine = setup_engine();
let result = engine.extract_interface(
"Dog",
&PathBuf::from("/tmp/test_refactor/main.cpp"),
"IBarkable",
&["speak()".to_string(), "fetch()".to_string()],
None,
);
assert!(result.success);
}
#[test]
fn test_extract_interface_no_methods() {
let mut engine = setup_engine();
let result = engine.extract_interface(
"Dog",
&PathBuf::from("/tmp/test_refactor/main.cpp"),
"IEmpty",
&[],
None,
);
assert!(!result.success);
}
#[test]
fn test_extract_interface_invalid_name() {
let mut engine = setup_engine();
let result = engine.extract_interface(
"Dog",
&PathBuf::from("/tmp/test_refactor/main.cpp"),
"123Bad",
&["speak()".to_string()],
None,
);
assert!(!result.success);
}
#[test]
fn test_extract_class() {
let mut engine = setup_engine();
let result = engine.extract_class(
"Dog",
&PathBuf::from("/tmp/test_refactor/main.cpp"),
"PetBase",
&["name".to_string()],
None,
);
assert!(result.success);
}
#[test]
fn test_extract_class_empty_names() {
let mut engine = setup_engine();
let result = engine.extract_class(
"",
&PathBuf::from("/tmp/test_refactor/main.cpp"),
"Base",
&["member".to_string()],
None,
);
assert!(!result.success);
}
#[test]
fn test_pull_up() {
let mut engine = setup_engine();
let result = engine.pull_up(
"Dog",
"Animal",
&PathBuf::from("/tmp/test_refactor/main.cpp"),
&["fetch()".to_string()],
);
assert!(result.success);
}
#[test]
fn test_push_down() {
let mut engine = setup_engine();
let result = engine.push_down(
"Animal",
"Dog",
&PathBuf::from("/tmp/test_refactor/main.cpp"),
&["name".to_string()],
);
assert!(result.success);
}
#[test]
fn test_pull_up_empty_names() {
let mut engine = setup_engine();
let result = engine.pull_up(
"",
"Animal",
&PathBuf::from("/tmp/test_refactor/main.cpp"),
&[],
);
assert!(!result.success);
}
#[test]
fn test_replace_type() {
let mut engine = setup_basic_engine();
let result = engine.replace_type("int", "int32_t", None, true, true, true);
assert!(result.success);
assert!(!result.changes.is_empty());
}
#[test]
fn test_replace_type_no_occurrences() {
let mut engine = setup_basic_engine();
let result = engine.replace_type("SomeNonexistentType", "int", None, true, false, false);
assert!(!result.success);
}
#[test]
fn test_replace_type_empty() {
let mut engine = setup_basic_engine();
let result = engine.replace_type("", "int", None, true, false, false);
assert!(!result.success);
}
#[test]
fn test_add_parameter() {
let mut engine = setup_basic_engine();
let param = X86ParameterDesc::new("extra", "double", 2).with_default("0.0");
let result = engine.add_parameter(
"foo",
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
param,
true,
);
assert!(result.success);
}
#[test]
fn test_remove_parameter() {
let mut engine = setup_basic_engine();
let result = engine.remove_parameter(
"foo",
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
1,
true,
);
assert!(result.success);
}
#[test]
fn test_remove_parameter_out_of_range() {
let mut engine = setup_basic_engine();
let result = engine.remove_parameter(
"foo",
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
99,
false,
);
assert!(!result.success);
}
#[test]
fn test_reorder_parameters() {
let mut engine = setup_basic_engine();
let result = engine.reorder_parameters(
"foo",
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
&[1, 0],
true,
);
assert!(result.success);
}
#[test]
fn test_reorder_parameters_wrong_length() {
let mut engine = setup_basic_engine();
let result = engine.reorder_parameters(
"foo",
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
&[0],
false,
);
assert!(!result.success);
}
#[test]
fn test_replace_macro() {
let mut engine = setup_engine();
let result = engine.replace_macro(
"MAX_AGE",
&PathBuf::from("/tmp/test_refactor/animal.h"),
"constexpr",
"100",
);
assert!(result.success);
}
#[test]
fn test_expand_macro() {
let mut engine = setup_engine();
let result = engine.expand_macro(
"ANIMAL_NAME",
&PathBuf::from("/tmp/test_refactor/animal.h"),
"\"Unknown\"",
);
assert!(result.success || !result.success);
}
#[test]
fn test_extract_method() {
let mut engine = setup_engine();
let range = X86RefactorRange::new(
X86RefactorLocation::new(15, 29),
X86RefactorLocation::new(15, 74),
);
let result = engine.extract_method(
"Dog",
&PathBuf::from("/tmp/test_refactor/main.cpp"),
&range,
"doFetch",
Some("void"),
);
assert!(result.success);
}
#[test]
fn test_convert_to_lambda() {
let mut engine = setup_basic_engine();
let result =
engine.convert_to_lambda("bar", &PathBuf::from("/tmp/test_refactor_basic/main.c"));
assert!(result.success || !result.success);
}
#[test]
fn test_convert_from_lambda() {
let mut engine = setup_basic_engine();
let result = engine.convert_from_lambda(
"result",
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
"get_result",
);
assert!(result.success || !result.success);
}
#[test]
fn test_add_override() {
let mut engine = setup_engine();
let result = engine.add_override(
"Dog",
&PathBuf::from("/tmp/test_refactor/main.cpp"),
&["speak()".to_string()],
);
assert!(result.success || !result.success);
}
#[test]
fn test_simplify_qualifications() {
let mut engine = setup_engine();
let result = engine.simplify_qualifications(
&PathBuf::from("/tmp/test_refactor/main.cpp"),
&["std".to_string()],
);
assert!(result.success);
assert!(!result.changes.is_empty());
}
#[test]
fn test_preview_generation() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let preview = X86RefactorPreview::new(engine);
assert!(!preview.diffs.is_empty());
assert!(preview.impact.files_affected > 0);
}
#[test]
fn test_preview_unified_diff() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let preview = X86RefactorPreview::new(engine);
let diff = preview.to_unified_diff();
assert!(diff.contains("foo"));
assert!(diff.contains("baz"));
}
#[test]
fn test_preview_risk_assessment() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let preview = X86RefactorPreview::new(engine);
assert!(preview.impact.risk_level <= X86RefactorRisk::Medium);
}
#[test]
fn test_preview_conflict_detection() {
let mut engine = setup_basic_engine();
let range = X86RefactorRange::new(
X86RefactorLocation::new(1, 1),
X86RefactorLocation::new(1, 20),
);
engine.record_change(X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Replace,
PathBuf::from("/tmp/test_refactor_basic/main.c"),
range,
"new_text_a".to_string(),
"Change A".to_string(),
));
engine.record_change(X86RefactoringChange::new(
X86RefactoringKind::ReplaceType,
X86ChangeType::Replace,
PathBuf::from("/tmp/test_refactor_basic/main.c"),
range,
"new_text_b".to_string(),
"Change B".to_string(),
));
let preview = X86RefactorPreview::new(engine);
assert!(!preview.conflicts.is_empty());
}
#[test]
fn test_preview_is_safe() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let preview = X86RefactorPreview::new(engine);
assert!(preview.is_safe());
}
#[test]
fn test_validator_semantic_check() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let mut validator =
X86RefactorValidator::new(engine, PathBuf::from("/tmp/test_refactor_basic"));
validator.check_build = false;
validator.check_tests = false;
let status = validator.validate();
assert!(status == X86ValidationStatus::Passed || status == X86ValidationStatus::Failed);
}
#[test]
fn test_validator_starts_not_run() {
let engine = setup_basic_engine();
let validator = X86RefactorValidator::new(engine, PathBuf::from("/tmp"));
assert_eq!(validator.status, X86ValidationStatus::NotRun);
}
#[test]
fn test_validator_with_build_check() {
let engine = setup_basic_engine();
let validator = X86RefactorValidator::new(engine, PathBuf::from("/tmp"))
.with_build_check("echo build ok");
assert!(validator.check_build);
assert_eq!(validator.build_command, Some("echo build ok".to_string()));
}
#[test]
fn test_validator_with_test_check() {
let engine = setup_basic_engine();
let validator = X86RefactorValidator::new(engine, PathBuf::from("/tmp"))
.with_test_check("echo test ok");
assert!(validator.check_tests);
assert_eq!(validator.test_command, Some("echo test ok".to_string()));
}
#[test]
fn test_validator_summary() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let mut validator =
X86RefactorValidator::new(engine, PathBuf::from("/tmp/test_refactor_basic"));
validator.check_build = false;
validator.check_tests = false;
validator.validate();
let summary = validator.summary();
assert!(summary.contains("Validation Status"));
}
#[test]
fn test_make_x86_refactor() {
let engine = make_x86_refactor(PathBuf::from("/tmp"));
assert!(!engine.verbose);
}
#[test]
fn test_make_x86_refactor_verbose() {
let engine = make_x86_refactor_verbose(PathBuf::from("/tmp"));
assert!(engine.verbose);
assert!(engine.cross_file);
}
#[test]
fn test_x86_rename_with_preview() {
let mut engine = setup_basic_engine();
let (result, preview, status) = x86_rename_with_preview(
&mut engine,
"foo",
"baz",
None,
&PathBuf::from("/tmp/test_refactor_basic"),
);
assert!(result.success);
assert!(!preview.diffs.is_empty());
assert!(status == X86ValidationStatus::Passed || status == X86ValidationStatus::Failed);
}
#[test]
fn test_load_file_from_string() {
let mut engine = X86Refactor::new(PathBuf::from("/tmp"));
let file = engine.load_file_from_string(
PathBuf::from("/tmp/test.c"),
"int main() { return 0; }".to_string(),
);
assert_eq!(file.line_count(), 1);
assert_eq!(file.language, X86SourceLanguage::C);
}
#[test]
fn test_source_file_text_in_range() {
let file = X86SourceFile::from_string(
PathBuf::from("/tmp/test.c"),
"line1\nline2\nline3".to_string(),
);
let range = X86RefactorRange::new(
X86RefactorLocation::new(2, 1),
X86RefactorLocation::new(2, 6),
);
let text = file.text_in_range(&range);
assert_eq!(text, Some("line2".to_string()));
}
#[test]
fn test_source_file_multiline_range() {
let file = X86SourceFile::from_string(
PathBuf::from("/tmp/test.c"),
"line1\nline2\nline3\nline4".to_string(),
);
let range = X86RefactorRange::new(
X86RefactorLocation::new(2, 1),
X86RefactorLocation::new(3, 6),
);
let text = file.text_in_range(&range);
assert_eq!(text, Some("line2\nline3".to_string()));
}
#[test]
fn test_source_file_invalid_range() {
let file =
X86SourceFile::from_string(PathBuf::from("/tmp/test.c"), "line1\nline2".to_string());
let range = X86RefactorRange::new(
X86RefactorLocation::new(5, 1),
X86RefactorLocation::new(10, 5),
);
assert_eq!(file.text_in_range(&range), None);
}
#[test]
fn test_index_symbols() {
let mut engine = setup_basic_engine();
let refs = engine.find_references("foo");
assert!(!refs.is_empty());
}
#[test]
fn test_find_references_in_file() {
let mut engine = setup_basic_engine();
let refs = engine
.find_references_in_file("foo", &PathBuf::from("/tmp/test_refactor_basic/main.c"));
assert!(!refs.is_empty());
}
#[test]
fn test_keywords_not_indexed() {
let mut engine = setup_basic_engine();
let refs = engine.find_references("int");
assert!(refs.is_empty());
}
#[test]
fn test_change_application_replace() {
let mut engine = X86Refactor::new(PathBuf::from("/tmp"));
engine.dry_run = false;
engine.load_file_from_string(PathBuf::from("/tmp/test.c"), "int x = 42;".to_string());
let range = X86RefactorRange::new(
X86RefactorLocation::new(1, 1),
X86RefactorLocation::new(1, 11),
);
engine.record_change(X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Replace,
PathBuf::from("/tmp/test.c"),
range,
"float y = 3.14".to_string(),
"Replace int with float".to_string(),
));
let result = engine.apply_changes();
assert!(result.is_ok());
let file = engine.get_file(&PathBuf::from("/tmp/test.c")).unwrap();
assert!(file.lines[0].contains("float y"));
}
#[test]
fn test_change_application_insert() {
let mut engine = X86Refactor::new(PathBuf::from("/tmp"));
engine.dry_run = false;
engine.load_file_from_string(PathBuf::from("/tmp/test.c"), "int x = 42;".to_string());
let loc = X86RefactorLocation::new(1, 1);
engine.record_change(X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Insert,
PathBuf::from("/tmp/test.c"),
X86RefactorRange::new(loc, loc),
"// comment\n".to_string(),
"Insert comment".to_string(),
));
let result = engine.apply_changes();
assert!(result.is_ok());
let file = engine.get_file(&PathBuf::from("/tmp/test.c")).unwrap();
assert!(file.content.contains("// comment"));
}
#[test]
fn test_change_application_delete() {
let mut engine = X86Refactor::new(PathBuf::from("/tmp"));
engine.dry_run = false;
engine.load_file_from_string(PathBuf::from("/tmp/test.c"), "int x = 42;".to_string());
let range = X86RefactorRange::new(
X86RefactorLocation::new(1, 1),
X86RefactorLocation::new(1, 5),
);
engine.record_change(X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Delete,
PathBuf::from("/tmp/test.c"),
range,
String::new(),
"Delete 'int '".to_string(),
));
let result = engine.apply_changes();
assert!(result.is_ok());
let file = engine.get_file(&PathBuf::from("/tmp/test.c")).unwrap();
assert!(!file.lines[0].starts_with("int"));
}
#[test]
fn test_dry_run_does_not_apply() {
let mut engine = X86Refactor::new(PathBuf::from("/tmp"));
engine.dry_run = true;
engine.load_file_from_string(PathBuf::from("/tmp/test.c"), "int x = 42;".to_string());
let range = X86RefactorRange::new(
X86RefactorLocation::new(1, 1),
X86RefactorLocation::new(1, 4),
);
engine.record_change(X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Replace,
PathBuf::from("/tmp/test.c"),
range,
"float".to_string(),
"Try replace".to_string(),
));
let result = engine.apply_changes();
assert!(result.is_ok());
let file = engine.get_file(&PathBuf::from("/tmp/test.c")).unwrap();
assert!(file.lines[0].starts_with("int"));
}
#[test]
fn test_parameter_desc_to_source() {
let param = X86ParameterDesc::new("count", "int", 0)
.with_default("0")
.as_const();
let src = param.to_source();
assert!(src.contains("const"));
assert!(src.contains("count"));
assert!(src.contains("= 0"));
}
#[test]
fn test_parameter_desc_reference() {
let param = X86ParameterDesc::new("value", "std::string", 0).as_reference();
let src = param.to_source();
assert!(src.contains("&"));
}
#[test]
fn test_parameter_desc_variadic() {
let param = X86ParameterDesc::new("args", "...", 0).as_variadic();
let src = param.to_source();
assert!(src.contains("..."));
}
#[test]
fn test_function_desc_to_signature() {
let mut func = X86FunctionDesc::new("compute", "int", PathBuf::from("/tmp/test.c"));
func.add_param(X86ParameterDesc::new("a", "int", 0));
func.add_param(
X86ParameterDesc::new("b", "double", 1)
.as_const()
.as_reference(),
);
let sig = func.to_signature();
assert!(sig.contains("compute"));
assert!(sig.contains("int a"));
assert!(sig.contains("const double& b"));
}
#[test]
fn test_function_desc_add_remove_param() {
let mut func = X86FunctionDesc::new("test", "void", PathBuf::from("/tmp/test.c"));
func.add_param(X86ParameterDesc::new("a", "int", 0));
func.add_param(X86ParameterDesc::new("b", "int", 1));
func.add_param(X86ParameterDesc::new("c", "int", 2));
assert_eq!(func.parameter_count(), 3);
let removed = func.remove_param(1);
assert!(removed.is_some());
assert_eq!(func.parameter_count(), 2);
assert_eq!(func.parameters[0].position, 0);
assert_eq!(func.parameters[1].position, 1);
}
#[test]
fn test_function_desc_reorder_params() {
let mut func = X86FunctionDesc::new("test", "void", PathBuf::from("/tmp/test.c"));
func.add_param(X86ParameterDesc::new("a", "int", 0));
func.add_param(X86ParameterDesc::new("b", "double", 1));
func.add_param(X86ParameterDesc::new("c", "char", 2));
let result = func.reorder_params(&[2, 0, 1]);
assert!(result.is_ok());
assert_eq!(func.parameters[0].name, "c");
assert_eq!(func.parameters[1].name, "a");
assert_eq!(func.parameters[2].name, "b");
}
#[test]
fn test_function_desc_reorder_invalid() {
let mut func = X86FunctionDesc::new("test", "void", PathBuf::from("/tmp/test.c"));
func.add_param(X86ParameterDesc::new("a", "int", 0));
let result = func.reorder_params(&[0, 1]); assert!(result.is_err());
let result2 = func.reorder_params(&[1]); assert!(result2.is_err());
}
#[test]
fn test_class_desc_members() {
let mut cls = X86ClassDesc::new("MyClass", PathBuf::from("/tmp/test.h"));
cls.fields.push(X86MemberDesc::new("x", "int"));
cls.fields.push(X86MemberDesc::new("name", "std::string"));
assert_eq!(cls.field_names(), vec!["x", "name"]);
}
#[test]
fn test_member_desc_to_declaration() {
let member = X86MemberDesc::new("count", "size_t");
let decl = member.to_declaration();
assert!(decl.contains("size_t count"));
assert!(decl.ends_with(';'));
}
#[test]
fn test_member_desc_static() {
let mut member = X86MemberDesc::new("instance_count", "int");
member.is_static = true;
let decl = member.to_declaration();
assert!(decl.starts_with("static"));
}
#[test]
fn test_location_display() {
let loc = X86RefactorLocation::new(10, 5);
assert_eq!(format!("{}", loc), "10:5");
}
#[test]
fn test_range_contains() {
let range = X86RefactorRange::new(
X86RefactorLocation::new(1, 1),
X86RefactorLocation::new(10, 20),
);
assert!(range.contains(X86RefactorLocation::new(5, 5)));
assert!(!range.contains(X86RefactorLocation::new(11, 1)));
assert!(!range.contains(X86RefactorLocation::new(0, 0)));
}
#[test]
fn test_range_overlaps() {
let a = X86RefactorRange::new(
X86RefactorLocation::new(1, 1),
X86RefactorLocation::new(5, 10),
);
let b = X86RefactorRange::new(
X86RefactorLocation::new(3, 1),
X86RefactorLocation::new(7, 20),
);
assert!(a.overlaps(&b));
assert!(b.overlaps(&a));
let c = X86RefactorRange::new(
X86RefactorLocation::new(6, 1),
X86RefactorLocation::new(10, 10),
);
assert!(!a.overlaps(&c));
}
#[test]
fn test_range_display() {
let range = X86RefactorRange::new(
X86RefactorLocation::new(1, 5),
X86RefactorLocation::new(10, 20),
);
assert!(format!("{}", range).contains("1:5"));
assert!(format!("{}", range).contains("10:20"));
}
#[test]
fn test_symbol_ref_builder() {
let sym = X86SymbolRef::new(
PathBuf::from("/tmp/test.c"),
X86RefactorRange::new(
X86RefactorLocation::new(1, 1),
X86RefactorLocation::new(1, 5),
),
X86SymbolKind::Function,
"main".to_string(),
)
.with_context("int main() {")
.as_declaration(true)
.as_definition(true)
.with_qualified_name("::main")
.with_parent_scope("global");
assert!(sym.is_declaration);
assert!(sym.is_definition);
assert_eq!(sym.qualified_name, Some("::main".to_string()));
assert_eq!(sym.parent_scope, Some("global".to_string()));
}
#[test]
fn test_symbol_kind_display() {
assert_eq!(format!("{}", X86SymbolKind::Function), "function");
assert_eq!(format!("{}", X86SymbolKind::Class), "class");
assert_eq!(format!("{}", X86SymbolKind::Variable), "variable");
assert_eq!(format!("{}", X86SymbolKind::Namespace), "namespace");
assert_eq!(format!("{}", X86SymbolKind::Macro), "macro");
}
#[test]
fn test_refactoring_kind_display() {
assert_eq!(format!("{}", X86RefactoringKind::Rename), "rename");
assert_eq!(
format!("{}", X86RefactoringKind::ExtractFunction),
"extract function"
);
assert_eq!(
format!("{}", X86RefactoringKind::InlineFunction),
"inline function"
);
}
#[test]
fn test_refactor_error_creation() {
let err = X86RefactorError::error("Something went wrong")
.at_file(PathBuf::from("/tmp/test.c"))
.at_location(X86RefactorLocation::new(5, 10))
.with_hint("Try again");
assert_eq!(err.severity, X86RefactorErrorSeverity::Error);
assert!(err.message.contains("Something went wrong"));
assert!(format!("{}", err).contains("5:10"));
}
#[test]
fn test_refactor_error_severity_display() {
assert_eq!(format!("{}", X86RefactorErrorSeverity::Warning), "warning");
assert_eq!(format!("{}", X86RefactorErrorSeverity::Fatal), "fatal");
}
#[test]
fn test_refactoring_result_errors_warnings() {
let result = X86RefactoringResult::failure(
X86RefactoringKind::Rename,
vec![
X86RefactorError::error("Error 1"),
X86RefactorError::warning("Warning 1"),
X86RefactorError::error("Error 2"),
],
);
assert_eq!(result.errors().len(), 2);
assert_eq!(result.warnings().len(), 1);
}
#[test]
fn test_change_type_display() {
assert_eq!(format!("{}", X86ChangeType::Replace), "replace");
assert_eq!(format!("{}", X86ChangeType::Insert), "insert");
assert_eq!(format!("{}", X86ChangeType::Delete), "delete");
}
#[test]
fn test_source_language_display() {
assert_eq!(format!("{}", X86SourceLanguage::C), "C");
assert_eq!(format!("{}", X86SourceLanguage::Cpp), "C++");
assert_eq!(format!("{}", X86SourceLanguage::Header), "Header");
}
#[test]
fn test_access_specifier_display() {
assert_eq!(format!("{}", X86AccessSpecifier::Public), "public");
assert_eq!(format!("{}", X86AccessSpecifier::Private), "private");
assert_eq!(format!("{}", X86AccessSpecifier::Unspecified), "");
}
#[test]
fn test_diff_type_display() {
assert_eq!(format!("{}", X86DiffType::Added), "+");
assert_eq!(format!("{}", X86DiffType::Removed), "-");
assert_eq!(format!("{}", X86DiffType::Modified), "~");
}
#[test]
fn test_refactor_risk_level() {
assert!(X86RefactorRisk::Low > X86RefactorRisk::Trivial);
assert!(X86RefactorRisk::Critical > X86RefactorRisk::High);
assert_eq!(format!("{}", X86RefactorRisk::Trivial), "trivial");
}
#[test]
fn test_validation_status_display() {
assert_eq!(format!("{}", X86ValidationStatus::Passed), "passed");
assert_eq!(format!("{}", X86ValidationStatus::Failed), "failed");
assert_eq!(format!("{}", X86ValidationStatus::NotRun), "not run");
}
#[test]
fn test_impact_analysis_default() {
let impact = X86ImpactAnalysis::default();
assert_eq!(impact.files_affected, 0);
assert_eq!(impact.risk_level, X86RefactorRisk::Trivial);
}
#[test]
fn test_impact_analysis_risk_trivial() {
let mut impact = X86ImpactAnalysis::default();
let risk = X86RefactorPreview::assess_risk(&impact);
assert_eq!(risk, X86RefactorRisk::Trivial);
}
#[test]
fn test_impact_analysis_risk_critical() {
let mut impact = X86ImpactAnalysis::default();
impact.files_affected = 50;
impact.lines_changed = 500;
impact.affects_headers = true;
impact.multi_tu = true;
let risk = X86RefactorPreview::assess_risk(&impact);
assert_eq!(risk, X86RefactorRisk::Critical);
}
#[test]
fn test_base_class_desc() {
let base = X86BaseClassDesc::new("BaseClass", X86AccessSpecifier::Public);
assert_eq!(base.class_name, "BaseClass");
assert_eq!(base.access, X86AccessSpecifier::Public);
assert!(!base.is_virtual);
}
#[test]
fn test_undo_last() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
engine.operation_history.push(X86RefactoringResult::success(
X86RefactoringKind::Rename,
engine.changes.clone(),
));
let result = engine.undo_last();
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_undo_no_history() {
let mut engine = setup_basic_engine();
let result = engine.undo_last();
assert!(result.is_err());
}
#[test]
fn test_pending_change_count() {
let mut engine = setup_basic_engine();
assert_eq!(engine.pending_change_count(), 0);
engine.rename("foo", "baz", None, None, None);
assert!(engine.pending_change_count() > 0);
}
#[test]
fn test_loaded_file_count() {
let engine = setup_basic_engine();
assert_eq!(engine.loaded_file_count(), 1);
}
#[test]
fn test_indexed_symbol_count() {
let engine = setup_basic_engine();
assert!(engine.indexed_symbol_count() > 0);
}
#[test]
fn test_clear_changes() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
assert!(engine.pending_change_count() > 0);
engine.clear_changes();
assert_eq!(engine.pending_change_count(), 0);
}
#[test]
fn test_clear_diagnostics() {
let mut engine = setup_basic_engine();
engine.add_diagnostic(X86RefactorError::warning("Test warning"));
engine.clear_diagnostics();
assert!(engine.diagnostics.is_empty());
}
#[test]
fn test_rename_to_very_long_name() {
let mut engine = setup_basic_engine();
let long_name = "a".repeat(500);
let result = engine.rename("foo", &long_name, None, None, None);
assert!(result.success);
}
#[test]
fn test_extract_function_empty_range() {
let mut engine = setup_basic_engine();
let range = X86RefactorRange::new(
X86RefactorLocation::new(1, 1),
X86RefactorLocation::new(1, 1),
);
let result = engine.extract_function(
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
&range,
"empty_func",
None,
);
assert!(result.success);
}
#[test]
fn test_replace_type_with_file_filter() {
let mut engine = setup_basic_engine();
let result = engine.replace_type(
"int",
"int32_t",
Some(&PathBuf::from("/tmp/test_refactor_basic/main.c")),
true,
false,
false,
);
assert!(result.success);
}
#[test]
fn test_reorder_parameters_duplicate_index() {
let mut engine = setup_basic_engine();
let result = engine.reorder_parameters(
"foo",
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
&[0, 0], false,
);
assert!(!result.success);
}
#[test]
fn test_full_rename_rename_back() {
let mut engine = setup_basic_engine();
let r1 = engine.rename("foo", "temp_name", None, None, None);
assert!(r1.success);
engine.clear_changes();
let r2 = engine.rename("temp_name", "foo", None, None, None);
assert!(r2.success);
}
#[test]
fn test_multiple_operations_sequence() {
let mut engine = setup_basic_engine();
let r1 = engine.rename("foo", "compute", None, None, None);
assert!(r1.success);
engine.clear_changes();
let r2 = engine.replace_type("int", "int32_t", None, true, false, false);
assert!(r2.success);
engine.clear_changes();
let range = X86RefactorRange::new(
X86RefactorLocation::new(2, 13),
X86RefactorLocation::new(2, 17),
);
let r3 = engine.extract_variable(
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
&range,
"the_ten",
None,
true,
);
assert!(r3.success);
}
#[test]
fn test_preview_after_multiple_operations() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
engine.rename("bar", "qux", None, None, None);
let preview = X86RefactorPreview::new(engine);
assert!(preview.diffs.len() >= 4);
}
#[test]
fn test_validator_all_checks() {
let engine = setup_basic_engine();
let mut validator =
X86RefactorValidator::new(engine, PathBuf::from("/tmp/test_refactor_basic"))
.with_build_check("echo build ok")
.with_test_check("echo test ok");
validator.validate();
assert!(validator.status == X86ValidationStatus::Passed);
}
#[test]
fn test_access_specifier_equality() {
assert_eq!(X86AccessSpecifier::Public, X86AccessSpecifier::Public);
assert_ne!(X86AccessSpecifier::Public, X86AccessSpecifier::Private);
}
#[test]
fn test_refactoring_kind_equality() {
assert_eq!(X86RefactoringKind::Rename, X86RefactoringKind::Rename);
assert_ne!(X86RefactoringKind::Rename, X86RefactoringKind::ReplaceType);
}
#[test]
fn test_symbol_ref_in_macro_flag() {
let mut sym = X86SymbolRef::new(
PathBuf::from("/tmp/test.c"),
X86RefactorRange::new(
X86RefactorLocation::new(1, 1),
X86RefactorLocation::new(1, 5),
),
X86SymbolKind::Macro,
"MAX".to_string(),
);
sym.in_macro = true;
assert!(sym.in_macro);
}
#[test]
fn test_refactoring_change_order() {
let change = X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Replace,
PathBuf::from("/tmp/test.c"),
X86RefactorRange::new(
X86RefactorLocation::new(1, 1),
X86RefactorLocation::new(1, 5),
),
"new".to_string(),
"test".to_string(),
)
.with_order(5);
assert_eq!(change.order_index, 5);
}
#[test]
fn test_refactoring_result_with_build_verified() {
let result = X86RefactoringResult::success(X86RefactoringKind::Rename, vec![])
.with_build_verified(true)
.with_test_verified(true)
.with_duration(500);
assert!(result.build_verified);
assert!(result.test_verified);
assert_eq!(result.duration_ms, 500);
}
#[test]
fn test_dry_run_flag() {
let mut engine = X86Refactor::new(PathBuf::from("/tmp"));
assert!(!engine.dry_run);
engine.dry_run = true;
assert!(engine.dry_run);
}
#[test]
fn test_verbose_flag() {
let mut engine = X86Refactor::new(PathBuf::from("/tmp"));
assert!(!engine.verbose);
engine.verbose = true;
assert!(engine.verbose);
}
#[test]
fn test_load_directory_exclusion() {
let mut engine = X86Refactor::new(PathBuf::from("/tmp"));
engine.excluded_paths.push(PathBuf::from("/tmp/excluded"));
assert_eq!(engine.excluded_paths.len(), 1);
}
#[test]
fn test_file_extensions_config() {
let engine = X86Refactor::new(PathBuf::from("/tmp"));
assert!(engine.source_extensions.contains(&"c".to_string()));
assert!(engine.source_extensions.contains(&"cpp".to_string()));
assert!(engine.header_extensions.contains(&"h".to_string()));
}
#[test]
fn test_change_limit_config() {
let engine = X86Refactor::new(PathBuf::from("/tmp"));
assert_eq!(engine.change_limit, 1000);
}
#[test]
fn test_auto_resolve_config() {
let engine = X86Refactor::new(PathBuf::from("/tmp"));
assert!(engine.auto_resolve);
}
#[test]
fn test_cross_file_config() {
let engine = X86Refactor::new(PathBuf::from("/tmp"));
assert!(engine.cross_file);
}
#[test]
fn test_preview_estimate_duration() {
let engine = setup_basic_engine();
let preview = X86RefactorPreview::new(engine);
let duration = preview.estimate_duration();
assert!(duration > 0);
}
#[test]
fn test_preview_summary_default() {
let summary = X86PreviewSummary::default();
assert_eq!(summary.total_changes, 0);
assert!(summary.validation_passed);
}
#[test]
fn test_validator_without_semantic_check() {
let engine = setup_basic_engine();
let validator =
X86RefactorValidator::new(engine, PathBuf::from("/tmp")).without_semantic_check();
assert!(!validator.check_semantics);
}
#[test]
fn test_rename_scoped() {
let mut engine = setup_engine();
let result = engine.rename(
"age",
"years",
Some(X86SymbolKind::Field),
None,
Some("Animal"),
);
assert!(result.success || !result.success);
}
#[test]
fn test_change_signature_file_not_found() {
let mut engine = setup_basic_engine();
let result = engine.change_signature(
"foo",
&PathBuf::from("/tmp/nonexistent.c"),
vec![],
None,
false,
);
assert!(!result.success);
}
#[test]
fn test_extract_interface_class_not_found() {
let mut engine = setup_engine();
let result = engine.extract_interface(
"NoSuchClass",
&PathBuf::from("/tmp/test_refactor/main.cpp"),
"IFoo",
&["method()".to_string()],
None,
);
assert!(!result.success);
}
#[test]
fn test_is_valid_identifier() {
assert!(X86Refactor::_is_valid_identifier("valid_name"));
assert!(X86Refactor::_is_valid_identifier("_underscore"));
assert!(!X86Refactor::_is_valid_identifier("123start"));
assert!(!X86Refactor::_is_valid_identifier(""));
assert!(!X86Refactor::_is_valid_identifier("has space"));
}
#[test]
fn test_parameter_desc_attributes() {
let param = X86ParameterDesc::new("x", "int", 0)
.with_attributes(vec!["[[maybe_unused]]".to_string()]);
let src = param.to_source();
assert!(src.contains("[[maybe_unused]]"));
}
#[test]
fn test_function_desc_template() {
let mut func = X86FunctionDesc::new("max", "T", PathBuf::from("/tmp/test.cpp"));
func.is_template = true;
func.template_params = vec!["typename T".to_string()];
func.add_param(X86ParameterDesc::new("a", "T", 0).as_const().as_reference());
func.add_param(X86ParameterDesc::new("b", "T", 1).as_const().as_reference());
let sig = func.to_signature();
assert!(sig.contains("template<typename T>"));
assert!(sig.contains("const T& a"));
}
#[test]
fn test_class_desc_as_struct() {
let cls = X86ClassDesc::new("Point", PathBuf::from("/tmp/test.h")).as_struct();
assert!(cls.is_struct);
}
#[test]
fn test_preview_is_safe_with_conflicts() {
let mut engine = setup_basic_engine();
let range = X86RefactorRange::new(
X86RefactorLocation::new(1, 1),
X86RefactorLocation::new(1, 20),
);
engine.record_change(X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Replace,
PathBuf::from("/tmp/test_refactor_basic/main.c"),
range,
"a".to_string(),
"change A".to_string(),
));
engine.record_change(X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Replace,
PathBuf::from("/tmp/test_refactor_basic/main.c"),
range,
"b".to_string(),
"change B".to_string(),
));
let preview = X86RefactorPreview::new(engine);
assert!(!preview.is_safe());
}
#[test]
fn test_replace_macro_not_found() {
let mut engine = setup_basic_engine();
let result = engine.replace_macro(
"UNDEFINED_MACRO",
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
"constexpr",
"0",
);
assert!(!result.success);
}
#[test]
fn test_pull_up_class_not_found() {
let mut engine = setup_engine();
let result = engine.pull_up(
"Dog",
"NoSuchBase",
&PathBuf::from("/tmp/test_refactor/main.cpp"),
&["member".to_string()],
);
assert!(!result.success);
}
#[test]
fn test_simplify_control_flow_if_to_switch() {
let mut engine = setup_basic_engine();
let code = r#"
int classify(int x) {
if (x == 1) return 10;
else if (x == 2) return 20;
else if (x == 3) return 30;
return 0;
}
"#;
engine.load_file_from_string(PathBuf::from("/tmp/test_cf.c"), code.to_string());
engine.index_symbols();
let result = engine.simplify_control_flow(
&PathBuf::from("/tmp/test_cf.c"),
"classify",
"if_to_switch",
);
assert!(result.success);
}
#[test]
fn test_convert_switch_to_if() {
let mut engine = setup_basic_engine();
let code = r#"
int get_value(int x) {
switch (x) {
case 1: return 100;
case 2: return 200;
default: return 0;
}
}
"#;
engine.load_file_from_string(PathBuf::from("/tmp/test_switch.c"), code.to_string());
engine.index_symbols();
let result = engine.convert_switch_to_if(&PathBuf::from("/tmp/test_switch.c"), "get_value");
assert!(result.success);
}
#[test]
fn test_wrap_in_unique_ptr() {
let mut engine = setup_basic_engine();
let code = "int* ptr = new int(42);";
engine.load_file_from_string(PathBuf::from("/tmp/test_ptr.c"), code.to_string());
engine.index_symbols();
let result = engine.wrap_in_unique_ptr(&PathBuf::from("/tmp/test_ptr.c"), "ptr");
assert!(result.success);
}
#[test]
fn test_separate_declaration_and_initialization() {
let mut engine = setup_basic_engine();
let code = "int x = compute_value() + 10;";
engine.load_file_from_string(PathBuf::from("/tmp/test_sep.c"), code.to_string());
engine.index_symbols();
let result =
engine.separate_declaration_and_initialization(&PathBuf::from("/tmp/test_sep.c"), 1);
assert!(result.success);
}
#[test]
fn test_namespace_operations() {
let mut engine = setup_engine();
let result = engine.extract_namespace(
&PathBuf::from("/tmp/test_refactor/main.cpp"),
"Animal",
"zoo",
true,
);
assert!(result.success);
}
#[test]
fn test_inline_namespace_to_anonymous() {
let mut engine = setup_basic_engine();
let code = r#"
namespace detail {
int helper(int x) { return x * 2; }
}
"#;
engine.load_file_from_string(PathBuf::from("/tmp/test_ns.c"), code.to_string());
engine.index_symbols();
let result = engine.inline_namespace(&PathBuf::from("/tmp/test_ns.c"), "detail");
assert!(result.success);
}
#[test]
fn test_add_noexcept_specifier() {
let mut engine = setup_basic_engine();
let code = "int compute(int x) { return x * 2; }";
engine.load_file_from_string(PathBuf::from("/tmp/test_noex.c"), code.to_string());
engine.index_symbols();
let result = engine.add_noexcept(&PathBuf::from("/tmp/test_noex.c"), "compute", true);
assert!(result.success);
}
#[test]
fn test_add_const_qualifier() {
let mut engine = setup_basic_engine();
let code = r#"
class Counter {
public:
int getValue() { return val; }
private:
int val = 0;
};
"#;
engine.load_file_from_string(PathBuf::from("/tmp/test_const.c"), code.to_string());
engine.index_symbols();
let result = engine.add_const_qualifier(
&PathBuf::from("/tmp/test_const.c"),
"Counter",
&["getValue".to_string()],
);
assert!(result.success);
}
#[test]
fn test_convert_to_range_for() {
let mut engine = setup_basic_engine();
let code = r#"
void process(int* arr, int n) {
for (int i = 0; i < n; i++) {
arr[i] *= 2;
}
}
"#;
engine.load_file_from_string(PathBuf::from("/tmp/test_range.c"), code.to_string());
engine.index_symbols();
let result = engine.convert_to_range_for(&PathBuf::from("/tmp/test_range.c"), "process", 0);
assert!(!result.success);
}
#[test]
fn test_refactoring_session_create() {
let engine = setup_basic_engine();
let session = X86RefactoringSession::new(engine, "Test rename session");
assert!(!session.is_complete());
assert_eq!(session.name, "Test rename session");
}
#[test]
fn test_refactoring_session_add_operation() {
let engine = setup_basic_engine();
let mut session = X86RefactoringSession::new(engine, "Batch session");
session.add_operation(X86SessionOp {
kind: X86RefactoringKind::Rename,
description: "Rename foo to bar".to_string(),
enabled: true,
});
session.add_operation(X86SessionOp {
kind: X86RefactoringKind::ReplaceType,
description: "Replace int with int32".to_string(),
enabled: true,
});
assert_eq!(session.operation_count(), 2);
}
#[test]
fn test_refactoring_session_toggle_operation() {
let engine = setup_basic_engine();
let mut session = X86RefactoringSession::new(engine, "Toggle test");
session.add_operation(X86SessionOp {
kind: X86RefactoringKind::Rename,
description: "Op 1".to_string(),
enabled: true,
});
session.toggle_operation(0);
let ops = session.operations();
assert!(!ops[0].enabled);
session.toggle_operation(0);
let ops = session.operations();
assert!(ops[0].enabled);
}
#[test]
fn test_refactoring_session_clear() {
let engine = setup_basic_engine();
let mut session = X86RefactoringSession::new(engine, "Clear test");
session.add_operation(X86SessionOp {
kind: X86RefactoringKind::Rename,
description: "Op".to_string(),
enabled: true,
});
session.clear();
assert_eq!(session.operation_count(), 0);
}
#[test]
fn test_preview_to_html() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let preview = X86RefactorPreview::new(engine);
let html = preview.to_html_report();
assert!(html.contains("<html>"));
assert!(html.contains("Refactoring Preview"));
}
#[test]
fn test_preview_to_json() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let preview = X86RefactorPreview::new(engine);
let json = preview.to_json_report();
assert!(json.contains("\"total_changes\""));
assert!(json.contains("\"risk_level\""));
}
#[test]
fn test_preview_file_grouping() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let preview = X86RefactorPreview::new(engine);
let groups = preview.group_by_file();
assert!(!groups.is_empty());
}
#[test]
fn test_preview_diff_statistics() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let preview = X86RefactorPreview::new(engine);
let stats = preview.diff_statistics();
assert!(
stats.total_insertions > 0
|| stats.total_deletions > 0
|| stats.total_modifications > 0
);
}
#[test]
fn test_validator_detailed_semantic_checks() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let mut validator =
X86RefactorValidator::new(engine, PathBuf::from("/tmp/test_refactor_basic"));
validator.check_build = false;
validator.check_tests = false;
validator.run_detailed_semantic_checks();
assert!(!validator.results.is_empty());
}
#[test]
fn test_validator_check_name_collision() {
let mut engine = setup_basic_engine();
engine.rename("foo", "bar", None, None, None);
let mut validator =
X86RefactorValidator::new(engine, PathBuf::from("/tmp/test_refactor_basic"));
let has_collision = validator.check_name_collisions();
assert!(has_collision || !has_collision);
}
#[test]
fn test_validator_check_unchanged_semantics() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let mut validator =
X86RefactorValidator::new(engine, PathBuf::from("/tmp/test_refactor_basic"));
let semantics_ok = validator.check_unchanged_semantics();
assert!(semantics_ok);
}
#[test]
fn test_find_function_start() {
let engine = setup_basic_engine();
let loc =
engine.find_function_start(&PathBuf::from("/tmp/test_refactor_basic/main.c"), "foo");
assert!(loc.is_some());
}
#[test]
fn test_find_function_end() {
let engine = setup_basic_engine();
let loc =
engine.find_function_end(&PathBuf::from("/tmp/test_refactor_basic/main.c"), "foo");
assert!(loc.is_some());
}
#[test]
fn test_find_braces_in_code() {
let result = X86Refactor::find_matching_brace("int main() { int x = 42; return x; }", 10);
assert!(result.is_some());
}
#[test]
fn test_detect_indent_style() {
let engine = setup_basic_engine();
let style = engine.detect_indent_style(&PathBuf::from("/tmp/test_refactor_basic/main.c"));
assert!(style.contains("space") || style.contains("tab") || style.contains("mixed"));
}
#[test]
fn test_count_lines_of_code() {
let engine = setup_basic_engine();
let loc = engine.count_lines_of_code(&PathBuf::from("/tmp/test_refactor_basic/main.c"));
assert!(loc > 0);
}
#[test]
fn test_detect_code_style() {
let engine = setup_basic_engine();
let style = engine.detect_code_style(&PathBuf::from("/tmp/test_refactor_basic/main.c"));
assert!(!style.is_empty());
}
#[test]
fn test_refactoring_result_builder() {
let result = X86RefactoringResultBuilder::new(X86RefactoringKind::Rename)
.add_change(X86RefactoringChange::new(
X86RefactoringKind::Rename,
X86ChangeType::Replace,
PathBuf::from("/tmp/test.c"),
X86RefactorRange::new(
X86RefactorLocation::new(1, 1),
X86RefactorLocation::new(1, 5),
),
"bar".to_string(),
"Rename".to_string(),
))
.with_duration(300)
.with_build_verified(true)
.build();
assert!(result.success);
assert_eq!(result.changes.len(), 1);
assert!(result.build_verified);
}
#[test]
fn test_refactoring_result_builder_add_diagnostic() {
let result = X86RefactoringResultBuilder::new(X86RefactoringKind::Rename)
.add_diagnostic(X86RefactorError::warning("Test warning"))
.add_diagnostic(X86RefactorError::info("Info note"))
.build_failure();
assert!(!result.success);
assert_eq!(result.diagnostics.len(), 2);
}
#[test]
fn test_preview_xml_report() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let preview = X86RefactorPreview::new(engine);
let xml = preview.to_xml_report();
assert!(xml.contains("<refactoring_preview>"));
assert!(xml.contains("</refactoring_preview>"));
}
#[test]
fn test_inline_suggestions() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let preview = X86RefactorPreview::new(engine);
let suggestions = preview.get_inline_suggestions();
assert!(!suggestions.is_empty());
}
#[test]
fn test_inline_suggestion_apply() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let preview = X86RefactorPreview::new(engine);
let suggestions = preview.get_inline_suggestions();
if !suggestions.is_empty() {
let first = &suggestions[0];
let applied = first.apply(&mut engine);
assert!(applied.is_ok());
}
}
#[test]
fn test_refactoring_lock() {
let engine = setup_basic_engine();
let lock = X86RefactoringLock::new(engine);
assert!(lock.is_owned());
std::mem::drop(lock);
}
#[test]
fn test_refactoring_guard() {
let engine = setup_basic_engine();
{
let _guard = X86RefactoringGuard::acquire(engine.clone());
assert!(_guard.is_active());
}
}
#[test]
fn test_engine_clone_independent() {
let engine1 = setup_basic_engine();
let mut engine2 = engine1.clone();
engine2.rename("foo", "baz", None, None, None);
assert_eq!(engine1.changes.len(), 0);
}
#[test]
fn test_generate_patch_file() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let preview = X86RefactorPreview::new(engine);
let patch = preview.generate_patch();
assert!(patch.contains("---"));
assert!(patch.contains("+++"));
}
#[test]
fn test_apply_patch_from_string() {
let mut engine = setup_basic_engine();
let patch = r#"--- a/main.c
+++ b/main.c
@@ -1,3 +1,3 @@
-void foo(int a, int b) {
+void baz(int a, int b) {
return a + b;
}
"#;
let result = engine.apply_patch(patch);
assert!(result.is_ok());
}
#[test]
fn test_compute_code_metrics() {
let engine = setup_basic_engine();
let metrics = engine.compute_code_metrics();
assert!(metrics.total_files > 0);
assert!(metrics.total_lines > 0);
}
#[test]
fn test_metrics_get_file_complexity() {
let engine = setup_basic_engine();
let complexity =
engine.get_file_complexity(&PathBuf::from("/tmp/test_refactor_basic/main.c"));
assert!(complexity > 0);
}
#[test]
fn test_batch_preview() {
let mut engine1 = setup_basic_engine();
engine1.rename("foo", "baz", None, None, None);
let mut engine2 = setup_basic_engine();
engine2.rename("bar", "qux", None, None, None);
let batch = X86BatchPreview::new(vec![
X86RefactorPreview::new(engine1),
X86RefactorPreview::new(engine2),
]);
assert_eq!(batch.preview_count(), 2);
}
#[test]
fn test_batch_preview_merged_diffs() {
let mut engine1 = setup_basic_engine();
engine1.rename("foo", "baz", None, None, None);
let mut engine2 = setup_basic_engine();
engine2.rename("bar", "qux", None, None, None);
let batch = X86BatchPreview::new(vec![
X86RefactorPreview::new(engine1),
X86RefactorPreview::new(engine2),
]);
let merged = batch.merged_diff();
assert!(!merged.is_empty());
}
#[test]
fn test_change_tracker() {
let engine = setup_basic_engine();
let mut tracker = X86ChangeTracker::new(engine);
tracker.track_change(X86RefactoringKind::Rename, "foo -> bar");
tracker.track_change(X86RefactoringKind::ReplaceType, "int -> int32_t");
assert_eq!(tracker.change_count(), 2);
}
#[test]
fn test_change_tracker_undo() {
let engine = setup_basic_engine();
let mut tracker = X86ChangeTracker::new(engine);
tracker.track_change(X86RefactoringKind::Rename, "foo -> bar");
let undone = tracker.undo_last();
assert!(undone.is_some());
assert_eq!(tracker.change_count(), 0);
}
#[test]
fn test_change_tracker_history() {
let engine = setup_basic_engine();
let mut tracker = X86ChangeTracker::new(engine);
tracker.track_change(X86RefactoringKind::Rename, "a -> b");
tracker.track_change(X86RefactoringKind::ExtractFunction, "extract compute");
tracker.track_change(X86RefactoringKind::InlineFunction, "inline helper");
let history = tracker.history();
assert_eq!(history.len(), 3);
}
#[test]
fn test_resolve_symbol_full() {
let engine = setup_basic_engine();
let resolved = engine.resolve_symbol("foo");
assert!(resolved.is_some());
if let Some(info) = resolved {
assert_eq!(info.name, "foo");
}
}
#[test]
fn test_resolve_symbol_nonexistent() {
let engine = setup_basic_engine();
let resolved = engine.resolve_symbol("no_such_symbol_xyz");
assert!(resolved.is_none());
}
#[test]
fn test_serialize_refactor_config() {
let engine = setup_basic_engine();
let config = engine.serialize_config();
assert!(config.contains("project_root"));
assert!(config.contains("change_limit"));
}
#[test]
fn test_deserialize_refactor_config() {
let config = r#"
{
"project_root": "/tmp/test",
"change_limit": 500,
"dry_run": true,
"verbose": false,
"cross_file": true,
"auto_resolve": false
}
"#;
let engine = X86Refactor::deserialize_config(config);
assert!(engine.is_ok());
let engine = engine.unwrap();
assert_eq!(engine.change_limit, 500);
assert!(engine.dry_run);
}
#[test]
fn test_benchmark_rename_large_codebase() {
let mut engine = X86Refactor::new(PathBuf::from("/tmp/bench"));
engine.dry_run = true;
let mut large_code = String::new();
for i in 0..100 {
large_code.push_str(&format!(
"void func{}(int x) {{ int y{} = x + {}; }}\n",
i, i, i
));
}
engine.load_file_from_string(PathBuf::from("/tmp/bench/large.c"), large_code);
engine.index_symbols();
let start = std::time::Instant::now();
let result = engine.rename("func0", "renamed_func", None, None, None);
let elapsed = start.elapsed();
assert!(result.success);
assert!(elapsed.as_millis() < 5000); }
#[test]
fn test_benchmark_replace_type_large() {
let mut engine = X86Refactor::new(PathBuf::from("/tmp/bench2"));
engine.dry_run = true;
let mut large_code = String::new();
for _ in 0..200 {
large_code.push_str("int x = 0; int y = x + 1; int z = y * 2;\n");
}
engine.load_file_from_string(PathBuf::from("/tmp/bench2/replace.c"), large_code);
engine.index_symbols();
let start = std::time::Instant::now();
let result = engine.replace_type("int", "int32_t", None, true, false, false);
let elapsed = start.elapsed();
assert!(result.success);
assert!(elapsed.as_millis() < 5000);
}
#[test]
fn test_reformat_after_refactoring() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let result = engine.reformat_file(&PathBuf::from("/tmp/test_refactor_basic/main.c"));
assert!(result.is_ok());
}
#[test]
fn test_normalize_whitespace() {
let mut engine = setup_basic_engine();
let result = engine.normalize_whitespace(&PathBuf::from("/tmp/test_refactor_basic/main.c"));
assert!(result.is_ok());
}
#[test]
fn test_add_include_directive() {
let mut engine = setup_basic_engine();
let result = engine.add_include(
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
"<stdbool.h>",
false,
);
assert!(result.is_ok());
}
#[test]
fn test_remove_include_directive() {
let engine = setup_basic_engine();
let result = engine.remove_include(
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
"<stdbool.h>",
);
assert!(result.is_ok());
}
#[test]
fn test_add_documentation_comment() {
let mut engine = setup_basic_engine();
let result = engine.add_documentation(
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
"foo",
"Brief description of foo",
"Detailed documentation",
);
assert!(result.is_ok());
}
#[test]
fn test_update_comment_references() {
let mut engine = setup_basic_engine();
engine.rename("foo", "baz", None, None, None);
let result = engine.update_comment_references(
&PathBuf::from("/tmp/test_refactor_basic/main.c"),
"foo",
"baz",
);
assert!(result.is_ok());
}
#[test]
fn test_comprehensive_workflow() {
let mut engine = setup_basic_engine();
let r1 = engine.rename("foo", "compute_sum", None, None, None);
assert!(r1.success);
let preview1 = X86RefactorPreview::new(engine.clone());
assert!(preview1.is_safe());
engine.clear_changes();
let r2 = engine.replace_type("int", "int32_t", None, true, false, false);
assert!(r2.success);
let preview2 = X86RefactorPreview::new(engine.clone());
let diff = preview2.to_unified_diff();
assert!(!diff.is_empty());
engine.clear_changes();
engine.rename("compute_sum", "compute_total", None, None, None);
let mut validator =
X86RefactorValidator::new(engine.clone(), PathBuf::from("/tmp/test_refactor_basic"));
validator.check_build = false;
validator.check_tests = false;
let status = validator.validate();
assert!(status == X86ValidationStatus::Passed);
let preview3 = X86RefactorPreview::new(engine.clone());
let xml = preview3.to_xml_report();
let json = preview3.to_json_report();
let html = preview3.to_html_report();
assert!(!xml.is_empty());
assert!(!json.is_empty());
assert!(!html.is_empty());
}
}