use std::cmp::min;
use std::collections::{HashMap, HashSet};
use std::fmt;
use super::diagnostics::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86FixItKind {
Replacement,
Insertion,
Removal,
Composition,
}
impl fmt::Display for X86FixItKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Replacement => write!(f, "replacement"),
Self::Insertion => write!(f, "insertion"),
Self::Removal => write!(f, "removal"),
Self::Composition => write!(f, "composition"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86FixItCategory {
MissingSemicolon,
MissingParen,
MissingBrace,
MissingBracket,
MissingColon,
MissingComma,
ExtraSemicolon,
ExtraBrace,
ExtraParen,
MissingInclude,
MissingStandardInclude,
MissingSystemInclude,
RedundantInclude,
TypoCorrection,
TypoInIdentifier,
TypoInKeyword,
TypoInOperator,
TypoInTypeName,
TypoInTemplate,
ConstQualification,
VolatileQualification,
RestrictQualification,
AutoTypeDeduction,
MissingTypename,
MissingTemplate,
MissingTypeSpecifier,
TypeConversion,
OverrideKeyword,
FinalKeyword,
NullptrConversion,
StaticCast,
DynamicCast,
ConstCast,
ReinterpretCast,
RangeForConversion,
MakeUnique,
MakeShared,
Constexpr,
Noexcept,
FormatString,
IntegerLiteralSuffix,
FloatLiteralSuffix,
MissingReturn,
MissingBreak,
MissingDefaultCase,
UnreachableCode,
UnusedVariable,
UnusedParameter,
UnusedInclude,
UnsafeFunction,
BufferOverflow,
FormatStringSecurity,
NullPointerDereference,
IntegerOverflow,
PassByValue,
PostIncrement,
ReserveBeforePushBack,
EmplaceBack,
MoveSemantics,
BraceStyle,
NamingConvention,
Indentation,
TrailingWhitespace,
LineLength,
SizeTFormat,
LongWidthDifference,
StructPacking,
EndianDependent,
AlignmentPortability,
Other,
Uncategorized,
}
impl fmt::Display for X86FixItCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::MissingSemicolon => "missing_semicolon",
Self::MissingParen => "missing_paren",
Self::MissingBrace => "missing_brace",
Self::MissingBracket => "missing_bracket",
Self::MissingColon => "missing_colon",
Self::MissingComma => "missing_comma",
Self::ExtraSemicolon => "extra_semicolon",
Self::ExtraBrace => "extra_brace",
Self::ExtraParen => "extra_paren",
Self::MissingInclude => "missing_include",
Self::MissingStandardInclude => "missing_standard_include",
Self::MissingSystemInclude => "missing_system_include",
Self::RedundantInclude => "redundant_include",
Self::TypoCorrection => "typo_correction",
Self::TypoInIdentifier => "typo_in_identifier",
Self::TypoInKeyword => "typo_in_keyword",
Self::TypoInOperator => "typo_in_operator",
Self::TypoInTypeName => "typo_in_type_name",
Self::TypoInTemplate => "typo_in_template",
Self::ConstQualification => "const_qualification",
Self::VolatileQualification => "volatile_qualification",
Self::RestrictQualification => "restrict_qualification",
Self::AutoTypeDeduction => "auto_type_deduction",
Self::MissingTypename => "missing_typename",
Self::MissingTemplate => "missing_template",
Self::MissingTypeSpecifier => "missing_type_specifier",
Self::TypeConversion => "type_conversion",
Self::OverrideKeyword => "override_keyword",
Self::FinalKeyword => "final_keyword",
Self::NullptrConversion => "nullptr_conversion",
Self::StaticCast => "static_cast",
Self::DynamicCast => "dynamic_cast",
Self::ConstCast => "const_cast",
Self::ReinterpretCast => "reinterpret_cast",
Self::RangeForConversion => "range_for_conversion",
Self::MakeUnique => "make_unique",
Self::MakeShared => "make_shared",
Self::Constexpr => "constexpr",
Self::Noexcept => "noexcept",
Self::FormatString => "format_string",
Self::IntegerLiteralSuffix => "integer_literal_suffix",
Self::FloatLiteralSuffix => "float_literal_suffix",
Self::MissingReturn => "missing_return",
Self::MissingBreak => "missing_break",
Self::MissingDefaultCase => "missing_default_case",
Self::UnreachableCode => "unreachable_code",
Self::UnusedVariable => "unused_variable",
Self::UnusedParameter => "unused_parameter",
Self::UnusedInclude => "unused_include",
Self::UnsafeFunction => "unsafe_function",
Self::BufferOverflow => "buffer_overflow",
Self::FormatStringSecurity => "format_string_security",
Self::NullPointerDereference => "null_pointer_dereference",
Self::IntegerOverflow => "integer_overflow",
Self::PassByValue => "pass_by_value",
Self::PostIncrement => "post_increment",
Self::ReserveBeforePushBack => "reserve_before_push_back",
Self::EmplaceBack => "emplace_back",
Self::MoveSemantics => "move_semantics",
Self::BraceStyle => "brace_style",
Self::NamingConvention => "naming_convention",
Self::Indentation => "indentation",
Self::TrailingWhitespace => "trailing_whitespace",
Self::LineLength => "line_length",
Self::SizeTFormat => "size_t_format",
Self::LongWidthDifference => "long_width_difference",
Self::StructPacking => "struct_packing",
Self::EndianDependent => "endian_dependent",
Self::AlignmentPortability => "alignment_portability",
Self::Other => "other",
Self::Uncategorized => "uncategorized",
};
write!(f, "{}", s)
}
}
impl X86FixItCategory {
pub fn is_syntax(&self) -> bool {
matches!(
self,
Self::MissingSemicolon
| Self::MissingParen
| Self::MissingBrace
| Self::MissingBracket
| Self::MissingColon
| Self::MissingComma
| Self::ExtraSemicolon
| Self::ExtraBrace
| Self::ExtraParen
)
}
pub fn is_typo(&self) -> bool {
matches!(
self,
Self::TypoCorrection
| Self::TypoInIdentifier
| Self::TypoInKeyword
| Self::TypoInOperator
| Self::TypoInTypeName
| Self::TypoInTemplate
)
}
pub fn is_modernization(&self) -> bool {
matches!(
self,
Self::OverrideKeyword
| Self::FinalKeyword
| Self::NullptrConversion
| Self::StaticCast
| Self::DynamicCast
| Self::ConstCast
| Self::ReinterpretCast
| Self::RangeForConversion
| Self::MakeUnique
| Self::MakeShared
| Self::Constexpr
| Self::Noexcept
| Self::AutoTypeDeduction
)
}
pub fn is_security(&self) -> bool {
matches!(
self,
Self::UnsafeFunction
| Self::BufferOverflow
| Self::FormatStringSecurity
| Self::NullPointerDereference
| Self::IntegerOverflow
)
}
pub fn is_performance(&self) -> bool {
matches!(
self,
Self::PassByValue
| Self::PostIncrement
| Self::ReserveBeforePushBack
| Self::EmplaceBack
| Self::MoveSemantics
)
}
pub fn priority(&self) -> u8 {
match self {
Self::MissingSemicolon
| Self::MissingParen
| Self::MissingBrace
| Self::MissingBracket => 0,
Self::MissingReturn | Self::MissingInclude => 1,
Self::TypoCorrection
| Self::TypoInIdentifier
| Self::TypoInKeyword
| Self::TypoInTypeName => 2,
Self::UnsafeFunction | Self::BufferOverflow | Self::NullPointerDereference => 3,
Self::PassByValue | Self::PostIncrement | Self::ConstQualification => 4,
Self::RangeForConversion | Self::NullptrConversion | Self::OverrideKeyword => 5,
Self::BraceStyle | Self::NamingConvention | Self::Indentation => 6,
Self::SizeTFormat | Self::LongWidthDifference | Self::StructPacking => 7,
_ => 8,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct X86FixItHint {
pub id: u64,
pub kind: X86FixItKind,
pub file: String,
pub start_line: usize,
pub start_col: usize,
pub end_line: usize,
pub end_col: usize,
pub text: String,
pub description: String,
pub category: X86FixItCategory,
pub diag_ids: Vec<DiagID>,
pub sub_hints: Vec<X86FixItHint>,
pub confidence: f64,
pub enabled: bool,
pub metadata: HashMap<String, String>,
}
impl X86FixItHint {
pub fn new_replacement(
id: u64,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
text: &str,
description: &str,
category: X86FixItCategory,
) -> Self {
Self {
id,
kind: X86FixItKind::Replacement,
file: file.to_string(),
start_line,
start_col,
end_line,
end_col,
text: text.to_string(),
description: description.to_string(),
category,
diag_ids: Vec::new(),
sub_hints: Vec::new(),
confidence: 1.0,
enabled: true,
metadata: HashMap::new(),
}
}
pub fn new_insertion(
id: u64,
file: &str,
line: usize,
col: usize,
text: &str,
description: &str,
category: X86FixItCategory,
) -> Self {
Self {
id,
kind: X86FixItKind::Insertion,
file: file.to_string(),
start_line: line,
start_col: col,
end_line: line,
end_col: col,
text: text.to_string(),
description: description.to_string(),
category,
diag_ids: Vec::new(),
sub_hints: Vec::new(),
confidence: 1.0,
enabled: true,
metadata: HashMap::new(),
}
}
pub fn new_removal(
id: u64,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
description: &str,
category: X86FixItCategory,
) -> Self {
Self {
id,
kind: X86FixItKind::Removal,
file: file.to_string(),
start_line,
start_col,
end_line,
end_col,
text: String::new(),
description: description.to_string(),
category,
diag_ids: Vec::new(),
sub_hints: Vec::new(),
confidence: 1.0,
enabled: true,
metadata: HashMap::new(),
}
}
pub fn new_composition(
id: u64,
file: &str,
sub_hints: Vec<X86FixItHint>,
description: &str,
category: X86FixItCategory,
) -> Self {
Self {
id,
kind: X86FixItKind::Composition,
file: file.to_string(),
start_line: 0,
start_col: 0,
end_line: 0,
end_col: 0,
text: String::new(),
description: description.to_string(),
category,
diag_ids: Vec::new(),
sub_hints,
confidence: 1.0,
enabled: true,
metadata: HashMap::new(),
}
}
pub fn with_diag(mut self, diag: DiagID) -> Self {
self.diag_ids.push(diag);
self
}
pub fn with_confidence(mut self, confidence: f64) -> Self {
self.confidence = confidence.clamp(0.0, 1.0);
self
}
pub fn with_meta(mut self, key: &str, value: &str) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
pub fn disabled(mut self) -> Self {
self.enabled = false;
self
}
pub fn is_high_confidence(&self) -> bool {
self.confidence >= 0.9
}
pub fn is_single_line(&self) -> bool {
self.start_line == self.end_line
}
pub fn has_text(&self) -> bool {
!self.text.is_empty()
}
pub fn change_size(&self) -> usize {
match self.kind {
X86FixItKind::Replacement => {
let range_len = if self.is_single_line() && self.end_col > self.start_col {
self.end_col - self.start_col
} else {
0
};
if self.text.len() > range_len {
self.text.len() - range_len
} else {
range_len - self.text.len()
}
}
X86FixItKind::Insertion => self.text.len(),
X86FixItKind::Removal => {
if self.is_single_line() && self.end_col > self.start_col {
self.end_col - self.start_col
} else {
0
}
}
X86FixItKind::Composition => self.sub_hints.iter().map(|h| h.change_size()).sum(),
}
}
pub fn flatten(&self) -> Vec<&X86FixItHint> {
match self.kind {
X86FixItKind::Composition => {
let mut result = Vec::new();
for sub in &self.sub_hints {
result.extend(sub.flatten());
}
result
}
_ => vec![self],
}
}
}
impl fmt::Display for X86FixItHint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"FixIt#{} [{}] {}:{}:{}-{}:{} — {} ({})",
self.id,
self.kind,
self.file,
self.start_line,
self.start_col,
self.end_line,
self.end_col,
self.description,
self.category
)
}
}
pub struct X86FixItEngine {
next_id: u64,
hints: Vec<X86FixItHint>,
pub typo_correction: X86TypoCorrection,
pub missing_include: X86MissingInclude,
pub code_modernization: X86CodeModernization,
pub security_fixit: X86SecurityFixIt,
pub performance_fixit: X86PerformanceFixIt,
pub style_fixit: X86StyleFixIt,
pub portability_fixit: X86PortabilityFixIt,
sources: HashMap<String, String>,
enabled_categories: HashSet<X86FixItCategory>,
min_confidence: f64,
max_hints: usize,
pub stats: X86FixItStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86FixItStats {
pub total_generated: usize,
pub syntax_hints: usize,
pub typo_hints: usize,
pub include_hints: usize,
pub modernization_hints: usize,
pub security_hints: usize,
pub performance_hints: usize,
pub style_hints: usize,
pub portability_hints: usize,
pub high_confidence: usize,
pub low_confidence: usize,
pub hints_applied: usize,
pub hints_rejected: usize,
pub composition_hints: usize,
}
impl X86FixItStats {
pub fn new() -> Self {
Self::default()
}
pub fn reset(&mut self) {
*self = Self::default();
}
}
impl X86FixItEngine {
pub fn new() -> Self {
let mut engine = Self {
next_id: 1,
hints: Vec::new(),
typo_correction: X86TypoCorrection::new(),
missing_include: X86MissingInclude::new(),
code_modernization: X86CodeModernization::new(),
security_fixit: X86SecurityFixIt::new(),
performance_fixit: X86PerformanceFixIt::new(),
style_fixit: X86StyleFixIt::new(),
portability_fixit: X86PortabilityFixIt::new(),
sources: HashMap::new(),
enabled_categories: HashSet::new(),
min_confidence: 0.5,
max_hints: 1000,
stats: X86FixItStats::new(),
};
engine.enable_all_categories();
engine
}
fn allocate_id(&mut self) -> u64 {
let id = self.next_id;
self.next_id += 1;
id
}
fn allocate_ids(&mut self, count: usize) -> Vec<u64> {
let start = self.next_id;
self.next_id += count as u64;
(start..self.next_id).collect()
}
fn enable_all_categories(&mut self) {
use X86FixItCategory::*;
for cat in &[
MissingSemicolon,
MissingParen,
MissingBrace,
MissingBracket,
MissingColon,
MissingComma,
ExtraSemicolon,
ExtraBrace,
ExtraParen,
MissingInclude,
MissingStandardInclude,
MissingSystemInclude,
RedundantInclude,
TypoCorrection,
TypoInIdentifier,
TypoInKeyword,
TypoInOperator,
TypoInTypeName,
TypoInTemplate,
ConstQualification,
VolatileQualification,
RestrictQualification,
AutoTypeDeduction,
MissingTypename,
MissingTemplate,
MissingTypeSpecifier,
TypeConversion,
OverrideKeyword,
FinalKeyword,
NullptrConversion,
StaticCast,
DynamicCast,
ConstCast,
ReinterpretCast,
RangeForConversion,
MakeUnique,
MakeShared,
Constexpr,
Noexcept,
FormatString,
IntegerLiteralSuffix,
FloatLiteralSuffix,
MissingReturn,
MissingBreak,
MissingDefaultCase,
UnreachableCode,
UnusedVariable,
UnusedParameter,
UnusedInclude,
UnsafeFunction,
BufferOverflow,
FormatStringSecurity,
NullPointerDereference,
IntegerOverflow,
PassByValue,
PostIncrement,
ReserveBeforePushBack,
EmplaceBack,
MoveSemantics,
BraceStyle,
NamingConvention,
Indentation,
TrailingWhitespace,
LineLength,
SizeTFormat,
LongWidthDifference,
StructPacking,
EndianDependent,
AlignmentPortability,
Other,
Uncategorized,
] {
self.enabled_categories.insert(*cat);
}
}
pub fn enable_category(&mut self, category: X86FixItCategory) {
self.enabled_categories.insert(category);
}
pub fn disable_category(&mut self, category: X86FixItCategory) {
self.enabled_categories.remove(&category);
}
pub fn is_category_enabled(&self, category: X86FixItCategory) -> bool {
self.enabled_categories.contains(&category)
}
pub fn set_min_confidence(&mut self, confidence: f64) {
self.min_confidence = confidence.clamp(0.0, 1.0);
}
pub fn set_max_hints(&mut self, max: usize) {
self.max_hints = max;
}
pub fn add_source(&mut self, file: &str, content: &str) {
self.sources.insert(file.to_string(), content.to_string());
}
pub fn remove_source(&mut self, file: &str) {
self.sources.remove(file);
}
pub fn get_source(&self, file: &str) -> Option<&str> {
self.sources.get(file).map(|s| s.as_str())
}
pub fn source_files(&self) -> Vec<&String> {
self.sources.keys().collect()
}
pub fn get_line(&self, file: &str, line: usize) -> Option<String> {
self.sources.get(file).and_then(|src| {
let lines: Vec<&str> = src.lines().collect();
if line > 0 && line <= lines.len() {
Some(lines[line - 1].to_string())
} else {
None
}
})
}
pub fn add_hint(&mut self, hint: X86FixItHint) {
if self.hints.len() >= self.max_hints {
return;
}
if !self.is_category_enabled(hint.category) {
return;
}
if hint.confidence < self.min_confidence {
return;
}
self.update_stats_for_hint(&hint);
self.hints.push(hint);
}
fn update_stats_for_hint(&mut self, hint: &X86FixItHint) {
self.stats.total_generated += 1;
if hint.is_high_confidence() {
self.stats.high_confidence += 1;
} else {
self.stats.low_confidence += 1;
}
if let X86FixItKind::Composition = hint.kind {
self.stats.composition_hints += 1;
}
if hint.category.is_syntax() {
self.stats.syntax_hints += 1;
} else if hint.category.is_typo() {
self.stats.typo_hints += 1;
} else if hint.category.is_modernization() {
self.stats.modernization_hints += 1;
} else if hint.category.is_security() {
self.stats.security_hints += 1;
} else if hint.category.is_performance() {
self.stats.performance_hints += 1;
}
}
pub fn hints(&self) -> &[X86FixItHint] {
&self.hints
}
pub fn hints_by_category(&self, category: X86FixItCategory) -> Vec<&X86FixItHint> {
self.hints
.iter()
.filter(|h| h.category == category)
.collect()
}
pub fn hints_by_file(&self, file: &str) -> Vec<&X86FixItHint> {
self.hints.iter().filter(|h| h.file == file).collect()
}
pub fn hints_by_priority(&self) -> Vec<&X86FixItHint> {
let mut sorted: Vec<&X86FixItHint> = self.hints.iter().collect();
sorted.sort_by_key(|h| (h.category.priority(), h.start_line, h.start_col));
sorted
}
pub fn high_confidence_hints(&self) -> Vec<&X86FixItHint> {
self.hints
.iter()
.filter(|h| h.is_high_confidence())
.collect()
}
pub fn enabled_hints(&self) -> Vec<&X86FixItHint> {
self.hints.iter().filter(|h| h.enabled).collect()
}
pub fn clear_hints(&mut self) {
self.hints.clear();
self.stats.reset();
self.next_id = 1;
}
pub fn reset(&mut self) {
self.clear_hints();
self.typo_correction = X86TypoCorrection::new();
self.missing_include = X86MissingInclude::new();
self.code_modernization = X86CodeModernization::new();
self.security_fixit = X86SecurityFixIt::new();
self.performance_fixit = X86PerformanceFixIt::new();
self.style_fixit = X86StyleFixIt::new();
self.portability_fixit = X86PortabilityFixIt::new();
self.enable_all_categories();
self.min_confidence = 0.5;
self.max_hints = 1000;
}
pub fn suggest_missing_semicolon(
&mut self,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
";",
"insert missing semicolon",
X86FixItCategory::MissingSemicolon,
)
.with_diag(DiagID::ErrExpectedSemiAfterExpr);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_missing_rparen(
&mut self,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
")",
"insert missing closing parenthesis",
X86FixItCategory::MissingParen,
)
.with_diag(DiagID::ErrExpectedRParen);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_missing_rbrace(
&mut self,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
"}",
"insert missing closing brace",
X86FixItCategory::MissingBrace,
)
.with_diag(DiagID::ErrExpectedRBrace);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_missing_rbracket(
&mut self,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
"]",
"insert missing closing bracket",
X86FixItCategory::MissingBracket,
)
.with_diag(DiagID::ErrExpectedRBracket);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_missing_colon(
&mut self,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
":",
"insert missing colon",
X86FixItCategory::MissingColon,
)
.with_diag(DiagID::ErrExpectedColon);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_missing_comma(
&mut self,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
", ",
"insert missing comma",
X86FixItCategory::MissingComma,
)
.with_diag(DiagID::ErrExpectedComma);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_remove_extra_semicolon(
&mut self,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_removal(
id,
file,
line,
col,
line,
col + 1,
"remove extra semicolon",
X86FixItCategory::ExtraSemicolon,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_remove_extra_parens(
&mut self,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_removal(
id,
file,
start_line,
start_col,
end_line,
end_col,
"remove redundant parentheses",
X86FixItCategory::ExtraParen,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_typo_correction(
&mut self,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
correction: &str,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let desc = format!("correct typo to '{}'", correction);
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
correction,
&desc,
X86FixItCategory::TypoCorrection,
)
.with_diag(DiagID::ErrUndeclaredIdentifier);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_keyword_correction(
&mut self,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
correction: &str,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let desc = format!("correct keyword to '{}'", correction);
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
correction,
&desc,
X86FixItCategory::TypoInKeyword,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_operator_correction(
&mut self,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
correction: &str,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let desc = format!("correct operator to '{}'", correction);
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
correction,
&desc,
X86FixItCategory::TypoInOperator,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_type_correction(
&mut self,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
correction: &str,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let desc = format!("correct type name to '{}'", correction);
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
correction,
&desc,
X86FixItCategory::TypoInTypeName,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_missing_include(
&mut self,
file: &str,
header: &str,
insertion_line: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let include_text = format!("#include <{}>", header);
let desc = format!("add missing #include <{}>", header);
let hint = X86FixItHint::new_insertion(
id,
file,
insertion_line,
1,
&include_text,
&desc,
X86FixItCategory::MissingInclude,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_missing_standard_include(
&mut self,
file: &str,
header: &str,
insertion_line: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let include_text = format!("#include <{}>", header);
let desc = format!("add missing standard library header <{}>", header);
let hint = X86FixItHint::new_insertion(
id,
file,
insertion_line,
1,
&include_text,
&desc,
X86FixItCategory::MissingStandardInclude,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_remove_include(&mut self, file: &str, line: usize) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_removal(
id,
file,
line,
1,
line,
999,
"remove redundant #include",
X86FixItCategory::RedundantInclude,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_const_qualification(
&mut self,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
"const ",
"add const qualifier",
X86FixItCategory::ConstQualification,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_override(
&mut self,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
" override",
"add override keyword to virtual function",
X86FixItCategory::OverrideKeyword,
)
.with_diag(DiagID::WSuggestOverride);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_final(&mut self, file: &str, line: usize, col: usize) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
" final",
"add final keyword",
X86FixItCategory::FinalKeyword,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_nullptr(
&mut self,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
"nullptr",
"use nullptr instead of NULL",
X86FixItCategory::NullptrConversion,
)
.with_diag(DiagID::WZeroAsNullPointerConstant);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_static_cast(
&mut self,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
target_type: &str,
expression: &str,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let replacement = format!("static_cast<{}>({})", target_type, expression);
let desc = format!("use static_cast<{}> instead of C-style cast", target_type);
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
&replacement,
&desc,
X86FixItCategory::StaticCast,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_dynamic_cast(
&mut self,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
target_type: &str,
expression: &str,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let replacement = format!("dynamic_cast<{}>({})", target_type, expression);
let desc = format!("use dynamic_cast<{}> for polymorphic downcast", target_type);
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
&replacement,
&desc,
X86FixItCategory::DynamicCast,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_auto(
&mut self,
file: &str,
line: usize,
col: usize,
end_col: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_replacement(
id,
file,
line,
col,
line,
end_col,
"auto",
"use auto type deduction",
X86FixItCategory::AutoTypeDeduction,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_missing_typename(
&mut self,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
"typename ",
"add missing typename keyword for dependent type",
X86FixItCategory::MissingTypename,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_missing_template_keyword(
&mut self,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
"template ",
"add missing template keyword for dependent name",
X86FixItCategory::MissingTemplate,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_format_string_fix(
&mut self,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
corrected_specifier: &str,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let desc = format!("correct format specifier to '{}'", corrected_specifier);
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
corrected_specifier,
&desc,
X86FixItCategory::FormatString,
)
.with_diag(DiagID::WFormatInvalidSpecifier);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_integer_suffix(
&mut self,
file: &str,
line: usize,
col: usize,
suffix: &str,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let desc = format!("add '{}' suffix to integer literal", suffix);
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
suffix,
&desc,
X86FixItCategory::IntegerLiteralSuffix,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_float_suffix(
&mut self,
file: &str,
line: usize,
col: usize,
suffix: &str,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let desc = format!("add '{}' suffix to float literal", suffix);
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
suffix,
&desc,
X86FixItCategory::FloatLiteralSuffix,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_missing_return(
&mut self,
file: &str,
line: usize,
col: usize,
return_value: &str,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let text = if return_value.is_empty() {
"return;".to_string()
} else {
format!("return {};", return_value)
};
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
&text,
"insert missing return statement",
X86FixItCategory::MissingReturn,
);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_remove_unused_variable(
&mut self,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
var_name: &str,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let desc = format!("remove unused variable '{}'", var_name);
let hint = X86FixItHint::new_removal(
id,
file,
start_line,
start_col,
end_line,
end_col,
&desc,
X86FixItCategory::UnusedVariable,
)
.with_diag(DiagID::WUnusedVariable);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_remove_unused_parameter(
&mut self,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
param_name: &str,
) -> Option<X86FixItHint> {
let id = self.allocate_id();
let desc = format!("remove unused parameter '{}'", param_name);
let hint = X86FixItHint::new_removal(
id,
file,
start_line,
start_col,
end_line,
end_col,
&desc,
X86FixItCategory::UnusedParameter,
)
.with_diag(DiagID::WUnusedParameter);
self.add_hint(hint);
self.hints().last().cloned()
}
pub fn suggest_include_guard(
&mut self,
file: &str,
guard_name: &str,
insertion_line_top: usize,
insertion_line_bottom: usize,
) -> Option<X86FixItHint> {
let ids = self.allocate_ids(2);
let top_text = format!("#ifndef {}\n#define {}\n", guard_name, guard_name);
let bottom_text = format!("#endif // {}\n", guard_name);
let top_hint = X86FixItHint::new_insertion(
ids[0],
file,
insertion_line_top,
1,
&top_text,
"add include guard start",
X86FixItCategory::Other,
);
let bottom_hint = X86FixItHint::new_insertion(
ids[1],
file,
insertion_line_bottom,
1,
&bottom_text,
"add include guard end",
X86FixItCategory::Other,
);
let comp = X86FixItHint::new_composition(
self.allocate_id(),
file,
vec![top_hint, bottom_hint],
&format!("generate include guard '{}'", guard_name),
X86FixItCategory::Other,
);
self.add_hint(comp);
self.hints().last().cloned()
}
pub fn generate_all(&mut self) -> usize {
let before = self.hints.len();
let file_paths: Vec<String> = self.sources.keys().cloned().collect();
let source_snapshots: HashMap<String, String> = self.sources.clone();
for file_path in &file_paths {
if let Some(source) = source_snapshots.get(file_path) {
let mut identifiers = Vec::new();
for line in source.lines() {
for word in line.split(|c: char| !c.is_alphanumeric() && c != '_') {
if !word.is_empty()
&& !self.typo_correction.keywords.contains(word)
&& word.len() > 1
{
identifiers.push(word.to_string());
}
}
}
self.typo_correction.register_scope(file_path, identifiers);
}
}
self.missing_include
.generate_all_hints(&mut self.hints, &source_snapshots);
self.code_modernization
.generate_all_hints(&mut self.hints, &source_snapshots);
self.security_fixit
.generate_all_hints(&mut self.hints, &source_snapshots);
self.performance_fixit
.generate_all_hints(&mut self.hints, &source_snapshots);
self.style_fixit
.generate_all_hints(&mut self.hints, &source_snapshots);
self.portability_fixit
.generate_all_hints(&mut self.hints, &source_snapshots);
self.hints.len() - before
}
pub fn apply_hint(&mut self, hint: &X86FixItHint, source: &str) -> Result<String, String> {
match hint.kind {
X86FixItKind::Replacement => self.apply_replacement(hint, source),
X86FixItKind::Insertion => self.apply_insertion(hint, source),
X86FixItKind::Removal => self.apply_removal(hint, source),
X86FixItKind::Composition => self.apply_composition(hint, source),
}
}
fn apply_replacement(&mut self, hint: &X86FixItHint, source: &str) -> Result<String, String> {
let lines: Vec<&str> = source.lines().collect();
if hint.start_line == 0 || hint.start_line > lines.len() {
return Err("invalid line range".into());
}
if hint.start_line == hint.end_line {
let line = lines[hint.start_line - 1];
if hint.start_col == 0 || hint.start_col > line.len() + 1 {
return Err("invalid column".into());
}
let before = &line[..hint.start_col - 1];
let after = if hint.end_col <= line.len() {
&line[hint.end_col - 1..]
} else {
""
};
let new_line = format!("{}{}{}", before, hint.text, after);
let mut result = String::new();
for (i, l) in lines.iter().enumerate() {
if i + 1 == hint.start_line {
result.push_str(&new_line);
} else {
result.push_str(l);
}
if i + 1 < lines.len() {
result.push('\n');
}
}
self.stats.hints_applied += 1;
Ok(result)
} else {
Err("multi-line replacements not yet supported".into())
}
}
fn apply_insertion(&mut self, hint: &X86FixItHint, source: &str) -> Result<String, String> {
let lines: Vec<&str> = source.lines().collect();
if hint.start_line == 0 || hint.start_line > lines.len() {
return Err("invalid insertion line".into());
}
let line = lines[hint.start_line - 1];
let insert_pos = if hint.start_col == 0 || hint.start_col > line.len() + 1 {
line.len()
} else {
hint.start_col - 1
};
let before = &line[..insert_pos];
let after = &line[insert_pos..];
let new_line = format!("{}{}{}", before, hint.text, after);
let mut result = String::new();
for (i, l) in lines.iter().enumerate() {
if i + 1 == hint.start_line {
result.push_str(&new_line);
} else {
result.push_str(l);
}
if i + 1 < lines.len() {
result.push('\n');
}
}
self.stats.hints_applied += 1;
Ok(result)
}
fn apply_removal(&mut self, hint: &X86FixItHint, source: &str) -> Result<String, String> {
let lines: Vec<&str> = source.lines().collect();
if hint.start_line == 0 || hint.start_line > lines.len() {
return Err("invalid line range for removal".into());
}
if hint.start_line == hint.end_line {
let line = lines[hint.start_line - 1];
if hint.start_col == 0 || hint.start_col > line.len() + 1 {
return Err("invalid column for removal".into());
}
let before = &line[..hint.start_col - 1];
let end = min(hint.end_col, line.len() + 1);
let after = if end <= line.len() {
&line[end - 1..]
} else {
""
};
let new_line = format!("{}{}", before, after);
let mut result = String::new();
for (i, l) in lines.iter().enumerate() {
if i + 1 == hint.start_line {
result.push_str(&new_line);
} else {
result.push_str(l);
}
if i + 1 < lines.len() {
result.push('\n');
}
}
self.stats.hints_applied += 1;
Ok(result)
} else {
Err("multi-line removals not yet supported".into())
}
}
fn apply_composition(&mut self, hint: &X86FixItHint, source: &str) -> Result<String, String> {
let mut current = source.to_string();
for sub in &hint.sub_hints {
match sub.kind {
X86FixItKind::Replacement => {
current = self.apply_replacement(sub, ¤t)?;
}
X86FixItKind::Insertion => {
current = self.apply_insertion(sub, ¤t)?;
}
X86FixItKind::Removal => {
current = self.apply_removal(sub, ¤t)?;
}
X86FixItKind::Composition => {
current = self.apply_composition(sub, ¤t)?;
}
}
}
self.stats.hints_applied += 1;
Ok(current)
}
pub fn apply_all(&mut self, source: &str) -> String {
let mut current = source.to_string();
let sorted: Vec<X86FixItHint> = {
let mut v: Vec<X86FixItHint> = self.hints.iter().cloned().collect();
v.sort_by_key(|h| (h.start_line, h.start_col));
v.reverse();
v
};
for hint in &sorted {
if let Ok(new_source) = self.apply_hint(hint, ¤t) {
current = new_source;
} else {
self.stats.hints_rejected += 1;
}
}
current
}
}
impl Default for X86FixItEngine {
fn default() -> Self {
Self::new()
}
}
pub struct X86TypoCorrection {
pub keywords: HashSet<String>,
operator_spellings: HashMap<String, String>,
type_names: HashSet<String>,
scope_identifiers: HashMap<String, Vec<String>>,
global_identifiers: Vec<String>,
max_edit_distance: usize,
min_candidates: usize,
distance_cache: HashMap<(String, String), usize>,
pub stats: TypoCorrectionStats,
}
#[derive(Debug, Clone, Default)]
pub struct TypoCorrectionStats {
pub corrections_attempted: usize,
pub corrections_found: usize,
pub exact_matches: usize,
pub keyword_corrections: usize,
pub identifier_corrections: usize,
pub operator_corrections: usize,
pub type_corrections: usize,
pub template_corrections: usize,
pub member_access_corrections: usize,
pub cache_hits: usize,
pub candidates_evaluated: usize,
}
impl TypoCorrectionStats {
pub fn new() -> Self {
Self::default()
}
}
impl X86TypoCorrection {
pub fn new() -> Self {
let mut engine = Self {
keywords: HashSet::new(),
operator_spellings: HashMap::new(),
type_names: HashSet::new(),
scope_identifiers: HashMap::new(),
global_identifiers: Vec::new(),
max_edit_distance: 3,
min_candidates: 5,
distance_cache: HashMap::new(),
stats: TypoCorrectionStats::new(),
};
engine.init_keywords();
engine.init_operators();
engine.init_type_names();
engine
}
fn init_keywords(&mut self) {
let c_keywords = [
"auto",
"break",
"case",
"char",
"const",
"continue",
"default",
"do",
"double",
"else",
"enum",
"extern",
"float",
"for",
"goto",
"if",
"inline",
"int",
"long",
"register",
"restrict",
"return",
"short",
"signed",
"sizeof",
"static",
"struct",
"switch",
"typedef",
"union",
"unsigned",
"void",
"volatile",
"while",
"_Alignas",
"_Alignof",
"_Atomic",
"_Bool",
"_Complex",
"_Generic",
"_Imaginary",
"_Noreturn",
"_Static_assert",
"_Thread_local",
];
let cpp_keywords = [
"alignas",
"alignof",
"and",
"and_eq",
"asm",
"bitand",
"bitor",
"bool",
"catch",
"char8_t",
"char16_t",
"char32_t",
"class",
"compl",
"concept",
"const_cast",
"consteval",
"constexpr",
"constinit",
"co_await",
"co_return",
"co_yield",
"decltype",
"delete",
"dynamic_cast",
"explicit",
"export",
"false",
"friend",
"mutable",
"namespace",
"new",
"noexcept",
"not",
"not_eq",
"nullptr",
"operator",
"or",
"or_eq",
"override",
"private",
"protected",
"public",
"reflexpr",
"reinterpret_cast",
"requires",
"static_cast",
"template",
"this",
"thread_local",
"throw",
"true",
"try",
"typeid",
"typename",
"using",
"virtual",
"wchar_t",
"xor",
"xor_eq",
];
for kw in c_keywords.iter() {
self.keywords.insert(kw.to_string());
}
for kw in cpp_keywords.iter() {
self.keywords.insert(kw.to_string());
}
}
fn init_operators(&mut self) {
let pairs = [
("and", "&&"),
("or", "||"),
("not", "!"),
("bitand", "&"),
("bitor", "|"),
("xor", "^"),
("compl", "~"),
("and_eq", "&="),
("or_eq", "|="),
("xor_eq", "^="),
("not_eq", "!="),
("&&", "&&"),
("||", "||"),
("!", "!"),
("&", "&"),
("|", "|"),
("^", "^"),
("~", "~"),
("&=", "&="),
("|=", "|="),
("^=", "^="),
("!=", "!="),
("==", "=="),
(">=", ">="),
("<=", "<="),
(">>", ">>"),
("<<", "<<"),
("+=", "+="),
("-=", "-="),
("*=", "*="),
("/=", "/="),
("%=", "%="),
("++", "++"),
("--", "--"),
("->", "->"),
("->*", "->*"),
(".*", ".*"),
("<=>", "<=>"),
];
for (alt, std) in pairs.iter() {
self.operator_spellings
.insert(alt.to_string(), std.to_string());
}
}
fn init_type_names(&mut self) {
let c_types = [
"char",
"signed char",
"unsigned char",
"short",
"unsigned short",
"int",
"unsigned int",
"long",
"unsigned long",
"long long",
"unsigned long long",
"float",
"double",
"long double",
"void",
"_Bool",
"bool",
"wchar_t",
"char16_t",
"char32_t",
"size_t",
"ssize_t",
"ptrdiff_t",
"intptr_t",
"uintptr_t",
"int8_t",
"int16_t",
"int32_t",
"int64_t",
"uint8_t",
"uint16_t",
"uint32_t",
"uint64_t",
"intmax_t",
"uintmax_t",
"int_least8_t",
"int_least16_t",
"int_least32_t",
"int_least64_t",
"uint_least8_t",
"uint_least16_t",
"uint_least32_t",
"uint_least64_t",
"int_fast8_t",
"int_fast16_t",
"int_fast32_t",
"int_fast64_t",
"uint_fast8_t",
"uint_fast16_t",
"uint_fast32_t",
"uint_fast64_t",
];
let cpp_types = [
"std::string",
"std::wstring",
"std::u8string",
"std::u16string",
"std::u32string",
"std::vector",
"std::list",
"std::map",
"std::set",
"std::unordered_map",
"std::unordered_set",
"std::shared_ptr",
"std::unique_ptr",
"std::weak_ptr",
"std::function",
"std::pair",
"std::tuple",
"std::array",
"std::optional",
"std::variant",
"std::any",
"std::string_view",
"std::span",
"std::initializer_list",
"std::allocator",
];
for t in c_types.iter() {
self.type_names.insert(t.to_string());
}
for t in cpp_types.iter() {
self.type_names.insert(t.to_string());
}
}
pub fn register_scope(&mut self, scope_name: &str, identifiers: Vec<String>) {
self.scope_identifiers
.insert(scope_name.to_string(), identifiers);
}
pub fn add_global_identifier(&mut self, identifier: &str) {
if !self.global_identifiers.contains(&identifier.to_string()) {
self.global_identifiers.push(identifier.to_string());
}
}
pub fn add_global_identifiers(&mut self, identifiers: &[String]) {
for id in identifiers {
self.add_global_identifier(id);
}
}
pub fn get_scope_identifiers(&self, scope: &str) -> Vec<String> {
let mut ids: Vec<String> = self.global_identifiers.clone();
if let Some(scope_ids) = self.scope_identifiers.get(scope) {
ids.extend(scope_ids.iter().cloned());
}
ids.sort();
ids.dedup();
ids
}
pub fn set_max_edit_distance(&mut self, distance: usize) {
self.max_edit_distance = distance;
}
pub fn edit_distance(&mut self, a: &str, b: &str) -> usize {
let cache_key = (a.to_string(), b.to_string());
if let Some(&dist) = self.distance_cache.get(&cache_key) {
self.stats.cache_hits += 1;
return dist;
}
let dist = Self::compute_levenshtein(a, b);
let cache_key_rev = (b.to_string(), a.to_string());
self.distance_cache.insert(cache_key, dist);
self.distance_cache.insert(cache_key_rev, dist);
dist
}
pub fn compute_levenshtein(a: &str, b: &str) -> usize {
let a_chars: Vec<char> = a.chars().collect();
let b_chars: Vec<char> = b.chars().collect();
let a_len = a_chars.len();
let b_len = b_chars.len();
if a_len == 0 {
return b_len;
}
if b_len == 0 {
return a_len;
}
let mut prev_row: Vec<usize> = (0..=b_len).collect();
let mut curr_row: Vec<usize> = vec![0; b_len + 1];
for i in 1..=a_len {
curr_row[0] = i;
for j in 1..=b_len {
let cost = if a_chars[i - 1] == b_chars[j - 1] {
0
} else {
1
};
curr_row[j] = min(
min(prev_row[j] + 1, curr_row[j - 1] + 1),
prev_row[j - 1] + cost,
);
if i > 1
&& j > 1
&& a_chars[i - 1] == b_chars[j - 2]
&& a_chars[i - 2] == b_chars[j - 1]
{
curr_row[j] = min(curr_row[j], prev_row[j - 2] + cost);
}
}
std::mem::swap(&mut prev_row, &mut curr_row);
}
prev_row[b_len]
}
pub fn similarity(&mut self, a: &str, b: &str) -> f64 {
let dist = self.edit_distance(a, b);
let max_len = a.len().max(b.len());
if max_len == 0 {
return 1.0;
}
1.0 - (dist as f64 / max_len as f64)
}
pub fn find_best_match(
&mut self,
typo: &str,
candidates: &[String],
) -> Option<(String, usize, f64)> {
if candidates.is_empty() {
return None;
}
self.stats.corrections_attempted += 1;
self.stats.candidates_evaluated += candidates.len();
let mut best: Option<(String, usize, f64)> = None;
for candidate in candidates {
let dist = self.edit_distance(typo, candidate);
if dist <= self.max_edit_distance {
let sim = self.similarity(typo, candidate);
match &best {
None => {
best = Some((candidate.clone(), dist, sim));
}
Some((_, best_dist, best_sim)) => {
if dist < *best_dist || (dist == *best_dist && sim > *best_sim) {
best = Some((candidate.clone(), dist, sim));
}
}
}
}
}
if best.is_some() {
self.stats.corrections_found += 1;
}
best
}
pub fn search_keywords(&mut self, typo: &str) -> Option<(String, usize, f64)> {
let candidates: Vec<String> = self.keywords.iter().cloned().collect();
let result = self.find_best_match(typo, &candidates);
if result.is_some() {
self.stats.keyword_corrections += 1;
}
result
}
pub fn search_type_names(&mut self, typo: &str) -> Option<(String, usize, f64)> {
let candidates: Vec<String> = self.type_names.iter().cloned().collect();
let result = self.find_best_match(typo, &candidates);
if result.is_some() {
self.stats.type_corrections += 1;
}
result
}
pub fn search_in_scope(&mut self, typo: &str, scope: &str) -> Option<(String, usize, f64)> {
let candidates = self.get_scope_identifiers(scope);
let result = self.find_best_match(typo, &candidates);
if result.is_some() {
self.stats.identifier_corrections += 1;
}
result
}
pub fn search_operator(&mut self, typo: &str) -> Option<(String, String, usize)> {
let candidates: Vec<String> = self.operator_spellings.keys().cloned().collect();
if let Some((best, dist, _sim)) = self.find_best_match(typo, &candidates) {
if let Some(std_op) = self.operator_spellings.get(&best) {
self.stats.operator_corrections += 1;
return Some((best, std_op.clone(), dist));
}
}
None
}
pub fn correct_typo(&mut self, typo: &str, scope: &str) -> Option<X86TypoCorrectionResult> {
self.stats.corrections_attempted += 1;
if self.keywords.contains(typo) || self.type_names.contains(typo) {
self.stats.exact_matches += 1;
return None; }
if let Some((correction, dist, confidence)) = self.search_keywords(typo) {
if dist <= 2 {
return Some(X86TypoCorrectionResult {
typo: typo.to_string(),
correction,
kind: X86TypoCorrectionKind::Keyword,
distance: dist,
confidence,
});
}
}
if let Some((correction, dist, confidence)) = self.search_in_scope(typo, scope) {
if dist < self.max_edit_distance {
return Some(X86TypoCorrectionResult {
typo: typo.to_string(),
correction,
kind: X86TypoCorrectionKind::Identifier,
distance: dist,
confidence,
});
}
}
if let Some((correction, dist, confidence)) = self.search_type_names(typo) {
if dist <= 2 {
return Some(X86TypoCorrectionResult {
typo: typo.to_string(),
correction,
kind: X86TypoCorrectionKind::TypeName,
distance: dist,
confidence,
});
}
}
self.stats.corrections_found += 1;
None
}
pub fn correct_unqualified_lookup(
&mut self,
name: &str,
scope: &str,
) -> Option<X86TypoCorrectionResult> {
let candidates = self.get_scope_identifiers(scope);
if let Some((correction, dist, confidence)) = self.find_best_match(name, &candidates) {
if dist < self.max_edit_distance {
return Some(X86TypoCorrectionResult {
typo: name.to_string(),
correction,
kind: X86TypoCorrectionKind::UnqualifiedLookup,
distance: dist,
confidence,
});
}
}
None
}
pub fn correct_member_access(
&mut self,
member_name: &str,
candidate_members: &[String],
) -> Option<X86TypoCorrectionResult> {
self.stats.member_access_corrections += 1;
if let Some((correction, dist, confidence)) =
self.find_best_match(member_name, candidate_members)
{
if dist < self.max_edit_distance {
return Some(X86TypoCorrectionResult {
typo: member_name.to_string(),
correction,
kind: X86TypoCorrectionKind::MemberAccess,
distance: dist,
confidence,
});
}
}
None
}
pub fn correct_template_name(
&mut self,
template_name: &str,
candidate_templates: &[String],
) -> Option<X86TypoCorrectionResult> {
self.stats.template_corrections += 1;
if let Some((correction, dist, confidence)) =
self.find_best_match(template_name, candidate_templates)
{
if dist < self.max_edit_distance {
return Some(X86TypoCorrectionResult {
typo: template_name.to_string(),
correction,
kind: X86TypoCorrectionKind::Template,
distance: dist,
confidence,
});
}
}
None
}
pub fn generate_all_hints(
&mut self,
_hints: &mut Vec<X86FixItHint>,
sources: &HashMap<String, String>,
) {
for (file, source) in sources {
let mut identifiers = Vec::new();
for line in source.lines() {
for word in line.split(|c: char| !c.is_alphanumeric() && c != '_') {
if !word.is_empty() && !self.keywords.contains(word) && word.len() > 1 {
identifiers.push(word.to_string());
}
}
}
self.register_scope(file, identifiers);
}
}
pub fn clear_cache(&mut self) {
self.distance_cache.clear();
}
pub fn reset_stats(&mut self) {
self.stats = TypoCorrectionStats::new();
}
}
impl Default for X86TypoCorrection {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct X86TypoCorrectionResult {
pub typo: String,
pub correction: String,
pub kind: X86TypoCorrectionKind,
pub distance: usize,
pub confidence: f64,
}
impl X86TypoCorrectionResult {
pub fn is_high_confidence(&self) -> bool {
self.distance <= 1
}
pub fn is_keyword_correction(&self) -> bool {
matches!(self.kind, X86TypoCorrectionKind::Keyword)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86TypoCorrectionKind {
Keyword,
Identifier,
TypeName,
Operator,
UnqualifiedLookup,
MemberAccess,
Template,
Unknown,
}
impl fmt::Display for X86TypoCorrectionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Keyword => write!(f, "keyword"),
Self::Identifier => write!(f, "identifier"),
Self::TypeName => write!(f, "type_name"),
Self::Operator => write!(f, "operator"),
Self::UnqualifiedLookup => write!(f, "unqualified_lookup"),
Self::MemberAccess => write!(f, "member_access"),
Self::Template => write!(f, "template"),
Self::Unknown => write!(f, "unknown"),
}
}
}
pub struct X86MissingInclude {
symbol_to_header: HashMap<String, String>,
symbol_to_alternatives: HashMap<String, Vec<String>>,
c_standard_headers: Vec<String>,
cpp_standard_headers: Vec<String>,
system_headers: Vec<String>,
insertion_locations: HashMap<String, usize>,
pub stats: MissingIncludeStats,
}
#[derive(Debug, Clone, Default)]
pub struct MissingIncludeStats {
pub symbols_checked: usize,
pub includes_suggested: usize,
pub headers_inferred: usize,
pub locations_detected: usize,
pub c_headers: usize,
pub cpp_headers: usize,
pub system_headers: usize,
}
impl MissingIncludeStats {
pub fn new() -> Self {
Self::default()
}
}
impl X86MissingInclude {
pub fn new() -> Self {
let mut engine = Self {
symbol_to_header: HashMap::new(),
symbol_to_alternatives: HashMap::new(),
c_standard_headers: Vec::new(),
cpp_standard_headers: Vec::new(),
system_headers: Vec::new(),
insertion_locations: HashMap::new(),
stats: MissingIncludeStats::new(),
};
engine.init_c_headers();
engine.init_cpp_headers();
engine.init_system_headers();
engine.init_symbol_mappings();
engine
}
fn init_c_headers(&mut self) {
self.c_standard_headers = vec![
"assert.h",
"complex.h",
"ctype.h",
"errno.h",
"fenv.h",
"float.h",
"inttypes.h",
"iso646.h",
"limits.h",
"locale.h",
"math.h",
"setjmp.h",
"signal.h",
"stdalign.h",
"stdarg.h",
"stdatomic.h",
"stdbool.h",
"stddef.h",
"stdint.h",
"stdio.h",
"stdlib.h",
"stdnoreturn.h",
"string.h",
"tgmath.h",
"threads.h",
"time.h",
"uchar.h",
"wchar.h",
"wctype.h",
]
.iter()
.map(|s| s.to_string())
.collect();
}
fn init_cpp_headers(&mut self) {
self.cpp_standard_headers = vec![
"algorithm",
"any",
"array",
"atomic",
"barrier",
"bit",
"bitset",
"charconv",
"chrono",
"codecvt",
"compare",
"complex",
"concepts",
"condition_variable",
"coroutine",
"deque",
"exception",
"execution",
"filesystem",
"format",
"forward_list",
"fstream",
"functional",
"future",
"initializer_list",
"iomanip",
"ios",
"iosfwd",
"iostream",
"istream",
"iterator",
"latch",
"limits",
"list",
"locale",
"map",
"memory",
"memory_resource",
"mutex",
"new",
"numbers",
"numeric",
"optional",
"ostream",
"queue",
"random",
"ranges",
"ratio",
"regex",
"scoped_allocator",
"semaphore",
"set",
"shared_mutex",
"source_location",
"span",
"sstream",
"stack",
"stdexcept",
"stop_token",
"streambuf",
"string",
"string_view",
"strstream",
"syncstream",
"system_error",
"thread",
"tuple",
"type_traits",
"typeindex",
"typeinfo",
"unordered_map",
"unordered_set",
"utility",
"valarray",
"variant",
"vector",
"version",
]
.iter()
.map(|s| s.to_string())
.collect();
}
fn init_system_headers(&mut self) {
self.system_headers = vec![
"unistd.h",
"fcntl.h",
"sys/types.h",
"sys/stat.h",
"sys/mman.h",
"sys/ioctl.h",
"sys/socket.h",
"sys/wait.h",
"netinet/in.h",
"arpa/inet.h",
"pthread.h",
"dlfcn.h",
"dirent.h",
"poll.h",
"sys/epoll.h",
"sys/eventfd.h",
"sys/signalfd.h",
"sys/timerfd.h",
"sys/inotify.h",
"cpuid.h",
"x86intrin.h",
"immintrin.h",
"xmmintrin.h",
"emmintrin.h",
"pmmintrin.h",
"tmmintrin.h",
"smmintrin.h",
"nmmintrin.h",
"wmmintrin.h",
"avxintrin.h",
"avx2intrin.h",
"avx512fintrin.h",
"bmiintrin.h",
"bmi2intrin.h",
"fmaintrin.h",
"f16cintrin.h",
]
.iter()
.map(|s| s.to_string())
.collect();
}
fn init_symbol_mappings(&mut self) {
let c_symbols: Vec<(&str, &str)> = vec![
("printf", "stdio.h"),
("fprintf", "stdio.h"),
("sprintf", "stdio.h"),
("snprintf", "stdio.h"),
("scanf", "stdio.h"),
("fscanf", "stdio.h"),
("sscanf", "stdio.h"),
("fopen", "stdio.h"),
("fclose", "stdio.h"),
("fread", "stdio.h"),
("fwrite", "stdio.h"),
("fgets", "stdio.h"),
("fputs", "stdio.h"),
("putchar", "stdio.h"),
("getchar", "stdio.h"),
("puts", "stdio.h"),
("gets", "stdio.h"),
("perror", "stdio.h"),
("remove", "stdio.h"),
("rename", "stdio.h"),
("tmpfile", "stdio.h"),
("tmpnam", "stdio.h"),
("fflush", "stdio.h"),
("setbuf", "stdio.h"),
("setvbuf", "stdio.h"),
("fseek", "stdio.h"),
("ftell", "stdio.h"),
("rewind", "stdio.h"),
("fgetpos", "stdio.h"),
("fsetpos", "stdio.h"),
("feof", "stdio.h"),
("ferror", "stdio.h"),
("clearerr", "stdio.h"),
("malloc", "stdlib.h"),
("calloc", "stdlib.h"),
("realloc", "stdlib.h"),
("free", "stdlib.h"),
("atoi", "stdlib.h"),
("atol", "stdlib.h"),
("atoll", "stdlib.h"),
("atof", "stdlib.h"),
("strtod", "stdlib.h"),
("strtof", "stdlib.h"),
("strtold", "stdlib.h"),
("strtol", "stdlib.h"),
("strtoll", "stdlib.h"),
("strtoul", "stdlib.h"),
("strtoull", "stdlib.h"),
("rand", "stdlib.h"),
("srand", "stdlib.h"),
("abort", "stdlib.h"),
("exit", "stdlib.h"),
("atexit", "stdlib.h"),
("_Exit", "stdlib.h"),
("getenv", "stdlib.h"),
("system", "stdlib.h"),
("bsearch", "stdlib.h"),
("qsort", "stdlib.h"),
("abs", "stdlib.h"),
("labs", "stdlib.h"),
("llabs", "stdlib.h"),
("div", "stdlib.h"),
("ldiv", "stdlib.h"),
("lldiv", "stdlib.h"),
("memcpy", "string.h"),
("memmove", "string.h"),
("memcmp", "string.h"),
("memchr", "string.h"),
("memset", "string.h"),
("strcpy", "string.h"),
("strncpy", "string.h"),
("strcat", "string.h"),
("strncat", "string.h"),
("strcmp", "string.h"),
("strncmp", "string.h"),
("strcoll", "string.h"),
("strxfrm", "string.h"),
("strchr", "string.h"),
("strrchr", "string.h"),
("strspn", "string.h"),
("strcspn", "string.h"),
("strpbrk", "string.h"),
("strstr", "string.h"),
("strtok", "string.h"),
("strlen", "string.h"),
("strerror", "string.h"),
("assert", "assert.h"),
("isalnum", "ctype.h"),
("isalpha", "ctype.h"),
("islower", "ctype.h"),
("isupper", "ctype.h"),
("isdigit", "ctype.h"),
("isxdigit", "ctype.h"),
("iscntrl", "ctype.h"),
("isgraph", "ctype.h"),
("isspace", "ctype.h"),
("isblank", "ctype.h"),
("isprint", "ctype.h"),
("ispunct", "ctype.h"),
("tolower", "ctype.h"),
("toupper", "ctype.h"),
("sin", "math.h"),
("cos", "math.h"),
("tan", "math.h"),
("asin", "math.h"),
("acos", "math.h"),
("atan", "math.h"),
("atan2", "math.h"),
("sinh", "math.h"),
("cosh", "math.h"),
("tanh", "math.h"),
("exp", "math.h"),
("log", "math.h"),
("log10", "math.h"),
("pow", "math.h"),
("sqrt", "math.h"),
("ceil", "math.h"),
("floor", "math.h"),
("fabs", "math.h"),
("fmod", "math.h"),
("time", "time.h"),
("clock", "time.h"),
("difftime", "time.h"),
("mktime", "time.h"),
("strftime", "time.h"),
("gmtime", "time.h"),
("localtime", "time.h"),
("asctime", "time.h"),
("ctime", "time.h"),
("offsetof", "stddef.h"),
("NULL", "stddef.h"),
("size_t", "stddef.h"),
("ptrdiff_t", "stddef.h"),
("max_align_t", "stddef.h"),
("wchar_t", "stddef.h"),
("INT32_MAX", "stdint.h"),
("INT64_MAX", "stdint.h"),
("UINT32_MAX", "stdint.h"),
("UINT64_MAX", "stdint.h"),
("int32_t", "stdint.h"),
("int64_t", "stdint.h"),
("uint32_t", "stdint.h"),
("uint64_t", "stdint.h"),
("va_start", "stdarg.h"),
("va_end", "stdarg.h"),
("va_arg", "stdarg.h"),
("va_copy", "stdarg.h"),
("va_list", "stdarg.h"),
("setjmp", "setjmp.h"),
("longjmp", "setjmp.h"),
("signal", "signal.h"),
("raise", "signal.h"),
("errno", "errno.h"),
("bool", "stdbool.h"),
("true", "stdbool.h"),
("false", "stdbool.h"),
("atomic_int", "stdatomic.h"),
("atomic_store", "stdatomic.h"),
("atomic_load", "stdatomic.h"),
("thread_t", "threads.h"),
("thrd_create", "threads.h"),
("thrd_join", "threads.h"),
("mtx_t", "threads.h"),
("mtx_lock", "threads.h"),
("mtx_unlock", "threads.h"),
("cnd_t", "threads.h"),
("cnd_wait", "threads.h"),
("cnd_signal", "threads.h"),
];
for (symbol, header) in c_symbols {
self.symbol_to_header
.insert(symbol.to_string(), header.to_string());
}
let cpp_symbols: Vec<(&str, &str)> = vec![
("std::string", "string"),
("std::wstring", "string"),
("std::vector", "vector"),
("std::list", "list"),
("std::map", "map"),
("std::set", "set"),
("std::unordered_map", "unordered_map"),
("std::unordered_set", "unordered_set"),
("std::shared_ptr", "memory"),
("std::unique_ptr", "memory"),
("std::weak_ptr", "memory"),
("std::make_shared", "memory"),
("std::make_unique", "memory"),
("std::function", "functional"),
("std::pair", "utility"),
("std::tuple", "tuple"),
("std::array", "array"),
("std::optional", "optional"),
("std::variant", "variant"),
("std::any", "any"),
("std::string_view", "string_view"),
("std::span", "span"),
("std::cout", "iostream"),
("std::cin", "iostream"),
("std::cerr", "iostream"),
("std::endl", "ostream"),
("std::move", "utility"),
("std::forward", "utility"),
("std::swap", "utility"),
("std::thread", "thread"),
("std::mutex", "mutex"),
("std::lock_guard", "mutex"),
("std::unique_lock", "mutex"),
("std::condition_variable", "condition_variable"),
("std::atomic", "atomic"),
("std::regex", "regex"),
("std::chrono::system_clock", "chrono"),
("std::chrono::steady_clock", "chrono"),
("std::chrono::duration", "chrono"),
("std::ifstream", "fstream"),
("std::ofstream", "fstream"),
("std::fstream", "fstream"),
("std::istringstream", "sstream"),
("std::ostringstream", "sstream"),
("std::stringstream", "sstream"),
("std::sort", "algorithm"),
("std::find", "algorithm"),
("std::copy", "algorithm"),
("std::transform", "algorithm"),
("std::for_each", "algorithm"),
("std::accumulate", "numeric"),
("std::iota", "numeric"),
("std::filesystem::path", "filesystem"),
("std::runtime_error", "stdexcept"),
("std::logic_error", "stdexcept"),
("std::exception", "exception"),
("std::iterator", "iterator"),
("std::begin", "iterator"),
("std::end", "iterator"),
];
for (symbol, header) in cpp_symbols {
self.symbol_to_header
.insert(symbol.to_string(), header.to_string());
}
}
pub fn get_header_for_symbol(&mut self, symbol: &str) -> Option<&String> {
self.stats.symbols_checked += 1;
self.symbol_to_header.get(symbol)
}
pub fn infer_header_from_symbol(&mut self, symbol: &str) -> Vec<String> {
self.stats.headers_inferred += 1;
let mut candidates = Vec::new();
if symbol.starts_with("is")
&& symbol.len() > 2
&& symbol.chars().nth(2).map_or(false, |c| c.is_lowercase())
{
candidates.push("ctype.h".to_string());
}
if symbol.starts_with("str") || symbol.starts_with("mem") {
candidates.push("string.h".to_string());
}
if matches!(
symbol,
"printf" | "sprintf" | "fprintf" | "scanf" | "fopen" | "fclose" | "fread" | "fwrite"
) {
candidates.push("stdio.h".to_string());
}
if matches!(
symbol,
"malloc" | "calloc" | "realloc" | "free" | "atoi" | "atof" | "rand"
) {
candidates.push("stdlib.h".to_string());
}
if matches!(
symbol,
"sin" | "cos" | "tan" | "sqrt" | "pow" | "log" | "exp" | "fabs" | "ceil" | "floor"
) {
candidates.push("math.h".to_string());
}
if symbol.starts_with("std::") {
let stripped = &symbol[5..]; if stripped.contains("string") {
candidates.push("string".to_string());
}
if stripped.contains("vector") {
candidates.push("vector".to_string());
}
if stripped.contains("map") {
candidates.push("map".to_string());
}
if stripped.contains("set") {
candidates.push("set".to_string());
}
if stripped.contains("shared_ptr")
|| stripped.contains("unique_ptr")
|| stripped.contains("make_")
{
candidates.push("memory".to_string());
}
if stripped.contains("cout") || stripped.contains("cin") || stripped.contains("cerr") {
candidates.push("iostream".to_string());
}
if stripped.contains("sort") || stripped.contains("find") || stripped.contains("copy") {
candidates.push("algorithm".to_string());
}
}
if candidates.is_empty() {
for header in &self.c_standard_headers {
let header_no_ext = header.trim_end_matches(".h");
if symbol.to_lowercase().contains(header_no_ext) {
candidates.push(header.clone());
}
}
}
candidates
}
pub fn find_insertion_location(&mut self, file: &str, source: &str) -> usize {
if let Some(&loc) = self.insertion_locations.get(file) {
return loc;
}
let lines: Vec<&str> = source.lines().collect();
let mut last_include_line = 0usize;
let mut first_non_directive_line = 0usize;
let mut in_block_comment = false;
for (i, line) in lines.iter().enumerate() {
let line_num = i + 1;
let trimmed = line.trim();
if in_block_comment {
if trimmed.contains("*/") {
in_block_comment = false;
}
continue;
}
if trimmed.starts_with("/*") {
in_block_comment = true;
continue;
}
if trimmed.starts_with("#include") || trimmed.starts_with("# include") {
last_include_line = line_num;
} else if trimmed.starts_with("#") {
continue;
} else if !trimmed.is_empty() && !trimmed.starts_with("//") {
if first_non_directive_line == 0 {
first_non_directive_line = line_num;
}
}
}
let loc = if last_include_line > 0 {
last_include_line + 1
} else if first_non_directive_line > 0 {
first_non_directive_line
} else {
1
};
self.insertion_locations.insert(file.to_string(), loc);
self.stats.locations_detected += 1;
loc
}
pub fn is_standard_symbol(&self, symbol: &str) -> bool {
self.symbol_to_header.contains_key(symbol)
}
pub fn c_headers(&self) -> &[String] {
&self.c_standard_headers
}
pub fn cpp_headers(&self) -> &[String] {
&self.cpp_standard_headers
}
pub fn system_headers(&self) -> &[String] {
&self.system_headers
}
pub fn generate_all_hints(
&mut self,
hints: &mut Vec<X86FixItHint>,
sources: &HashMap<String, String>,
) {
for (file, source) in sources {
let mut found_symbols = HashSet::new();
for (symbol, header) in &self.symbol_to_header.clone() {
if source.contains(symbol.as_str()) && !found_symbols.contains(symbol) {
let header_include = format!("#include <{}>", header);
let alt_include1 = format!("#include \"{}\"", header);
let found = source.contains(&header_include) || source.contains(&alt_include1);
if !found {
found_symbols.insert(symbol.clone());
let loc = self.find_insertion_location(file, source);
let hint = X86FixItHint::new_insertion(
hints.len() as u64 + 100000,
file,
loc,
1,
&format!("#include <{}>", header),
&format!("add missing standard library header <{}>", header),
X86FixItCategory::MissingStandardInclude,
);
hints.push(hint);
self.stats.includes_suggested += 1;
if self.c_standard_headers.contains(header) {
self.stats.c_headers += 1;
} else if self.cpp_standard_headers.contains(header) {
self.stats.cpp_headers += 1;
} else {
self.stats.system_headers += 1;
}
}
}
}
}
}
pub fn clear_locations(&mut self) {
self.insertion_locations.clear();
}
pub fn reset_stats(&mut self) {
self.stats = MissingIncludeStats::new();
}
}
impl Default for X86MissingInclude {
fn default() -> Self {
Self::new()
}
}
pub struct X86CodeModernization {
enabled_checks: HashSet<ModernizationCheck>,
pub stats: ModernizationStats,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ModernizationCheck {
RangeForLoop,
Nullptr,
Override,
AutoType,
MakeUnique,
MakeShared,
Constexpr,
Noexcept,
DefaultMemberInit,
UsingAlias,
EmplaceBack,
RawStringLiteral,
}
impl fmt::Display for ModernizationCheck {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RangeForLoop => write!(f, "modernize-loop-convert"),
Self::Nullptr => write!(f, "modernize-use-nullptr"),
Self::Override => write!(f, "modernize-use-override"),
Self::AutoType => write!(f, "modernize-use-auto"),
Self::MakeUnique => write!(f, "modernize-make-unique"),
Self::MakeShared => write!(f, "modernize-make-shared"),
Self::Constexpr => write!(f, "modernize-use-constexpr"),
Self::Noexcept => write!(f, "modernize-use-noexcept"),
Self::DefaultMemberInit => write!(f, "modernize-use-default-member-init"),
Self::UsingAlias => write!(f, "modernize-use-using"),
Self::EmplaceBack => write!(f, "modernize-use-emplace"),
Self::RawStringLiteral => write!(f, "modernize-raw-string-literal"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ModernizationStats {
pub range_for_suggestions: usize,
pub nullptr_suggestions: usize,
pub override_suggestions: usize,
pub auto_suggestions: usize,
pub make_unique_suggestions: usize,
pub make_shared_suggestions: usize,
pub constexpr_suggestions: usize,
pub noexcept_suggestions: usize,
pub default_member_init: usize,
pub using_alias: usize,
pub emplace_back: usize,
pub raw_string_literal: usize,
pub total_suggestions: usize,
}
impl ModernizationStats {
pub fn new() -> Self {
Self::default()
}
pub fn reset(&mut self) {
*self = Self::default();
}
}
impl X86CodeModernization {
pub fn new() -> Self {
let mut engine = Self {
enabled_checks: HashSet::new(),
stats: ModernizationStats::new(),
};
engine.enable_all();
engine
}
fn enable_all(&mut self) {
use ModernizationCheck::*;
for check in &[
RangeForLoop,
Nullptr,
Override,
AutoType,
MakeUnique,
MakeShared,
Constexpr,
Noexcept,
DefaultMemberInit,
UsingAlias,
EmplaceBack,
RawStringLiteral,
] {
self.enabled_checks.insert(*check);
}
}
pub fn enable(&mut self, check: ModernizationCheck) {
self.enabled_checks.insert(check);
}
pub fn disable(&mut self, check: ModernizationCheck) {
self.enabled_checks.remove(&check);
}
pub fn is_enabled(&self, check: ModernizationCheck) -> bool {
self.enabled_checks.contains(&check)
}
pub fn suggest_range_for<'a>(
&mut self,
engine: &'a mut X86FixItEngine,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
container: &str,
element: &str,
) -> Option<X86FixItHint> {
if !self.is_enabled(ModernizationCheck::RangeForLoop) {
return None;
}
let replacement = format!("for (auto&& {} : {})", element, container);
let desc = format!("convert to range-based for loop over '{}'", container);
engine.stats.modernization_hints += 1;
let hint = X86FixItHint::new_replacement(
engine.allocate_id(),
file,
start_line,
start_col,
end_line,
end_col,
&replacement,
&desc,
X86FixItCategory::RangeForConversion,
);
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_nullptr(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
) -> Option<X86FixItHint> {
if !self.is_enabled(ModernizationCheck::Nullptr) {
return None;
}
engine.suggest_nullptr(file, start_line, start_col, end_line, end_col)
}
pub fn suggest_override(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
if !self.is_enabled(ModernizationCheck::Override) {
return None;
}
engine.suggest_override(file, line, col)
}
pub fn suggest_auto(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
line: usize,
start_col: usize,
end_col: usize,
) -> Option<X86FixItHint> {
if !self.is_enabled(ModernizationCheck::AutoType) {
return None;
}
engine.suggest_auto(file, line, start_col, end_col)
}
pub fn suggest_make_unique(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
target_type: &str,
args: &str,
) -> Option<X86FixItHint> {
if !self.is_enabled(ModernizationCheck::MakeUnique) {
return None;
}
let replacement = if args.is_empty() {
format!("std::make_unique<{}>()", target_type)
} else {
format!("std::make_unique<{}>({})", target_type, args)
};
let desc = format!("use std::make_unique<{}> instead of new", target_type);
let hint = X86FixItHint::new_replacement(
engine.allocate_id(),
file,
start_line,
start_col,
end_line,
end_col,
&replacement,
&desc,
X86FixItCategory::MakeUnique,
);
engine.stats.modernization_hints += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_make_shared(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
target_type: &str,
args: &str,
) -> Option<X86FixItHint> {
if !self.is_enabled(ModernizationCheck::MakeShared) {
return None;
}
let replacement = if args.is_empty() {
format!("std::make_shared<{}>()", target_type)
} else {
format!("std::make_shared<{}>({})", target_type, args)
};
let desc = format!("use std::make_shared<{}> instead of new", target_type);
let hint = X86FixItHint::new_replacement(
engine.allocate_id(),
file,
start_line,
start_col,
end_line,
end_col,
&replacement,
&desc,
X86FixItCategory::MakeShared,
);
engine.stats.modernization_hints += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_constexpr(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
if !self.is_enabled(ModernizationCheck::Constexpr) {
return None;
}
let id = engine.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
"constexpr ",
"add constexpr specifier",
X86FixItCategory::Constexpr,
);
engine.stats.modernization_hints += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_noexcept(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
if !self.is_enabled(ModernizationCheck::Noexcept) {
return None;
}
let id = engine.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
" noexcept",
"add noexcept specifier",
X86FixItCategory::Noexcept,
);
engine.stats.modernization_hints += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_emplace_back(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
) -> Option<X86FixItHint> {
if !self.is_enabled(ModernizationCheck::EmplaceBack) {
return None;
}
let id = engine.allocate_id();
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
"emplace_back",
"use emplace_back instead of push_back",
X86FixItCategory::EmplaceBack,
);
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_using_alias(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
alias_name: &str,
type_name: &str,
) -> Option<X86FixItHint> {
if !self.is_enabled(ModernizationCheck::UsingAlias) {
return None;
}
let replacement = format!("using {} = {};", alias_name, type_name);
let id = engine.allocate_id();
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
&replacement,
"use using-alias instead of typedef",
X86FixItCategory::Other,
);
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn generate_all_hints(
&mut self,
_hints: &mut Vec<X86FixItHint>,
sources: &HashMap<String, String>,
) {
for (_file, source) in sources {
if self.is_enabled(ModernizationCheck::Nullptr) {
let patterns = ["NULL", "null", "0"];
for pat in &patterns {
if source.contains(pat) {
self.stats.nullptr_suggestions += 1;
}
}
}
if self.is_enabled(ModernizationCheck::RangeForLoop) {
if source.contains("for (") && source.contains("begin()") {
self.stats.range_for_suggestions += 1;
}
}
if self.is_enabled(ModernizationCheck::Override) {
if source.contains("virtual ") && !source.contains("override") {
self.stats.override_suggestions += 1;
}
}
if self.is_enabled(ModernizationCheck::AutoType) {
self.stats.auto_suggestions += 1;
}
if self.is_enabled(ModernizationCheck::MakeUnique) {
if source.contains("unique_ptr<") && source.contains("new ") {
self.stats.make_unique_suggestions += 1;
}
}
if self.is_enabled(ModernizationCheck::MakeShared) {
if source.contains("shared_ptr<") && source.contains("new ") {
self.stats.make_shared_suggestions += 1;
}
}
if self.is_enabled(ModernizationCheck::Constexpr) {
self.stats.constexpr_suggestions += 1;
}
if self.is_enabled(ModernizationCheck::Noexcept) {
self.stats.noexcept_suggestions += 1;
}
}
self.stats.total_suggestions = self.stats.nullptr_suggestions
+ self.stats.range_for_suggestions
+ self.stats.override_suggestions
+ self.stats.auto_suggestions
+ self.stats.make_unique_suggestions
+ self.stats.make_shared_suggestions
+ self.stats.constexpr_suggestions
+ self.stats.noexcept_suggestions;
}
pub fn reset_stats(&mut self) {
self.stats.reset();
}
}
impl Default for X86CodeModernization {
fn default() -> Self {
Self::new()
}
}
pub struct X86SecurityFixIt {
unsafe_to_safe: HashMap<String, SafeReplacement>,
pub stats: SecurityFixItStats,
}
#[derive(Debug, Clone)]
pub struct SafeReplacement {
pub function: String,
pub extra_args: Option<String>,
pub description: String,
pub severity: u8,
}
#[derive(Debug, Clone, Default)]
pub struct SecurityFixItStats {
pub gets_found: usize,
pub strcpy_found: usize,
pub sprintf_found: usize,
pub scanf_found: usize,
pub strcat_found: usize,
pub unsafe_calls_total: usize,
}
impl SecurityFixItStats {
pub fn new() -> Self {
Self::default()
}
pub fn reset(&mut self) {
*self = Self::default();
}
}
impl X86SecurityFixIt {
pub fn new() -> Self {
let mut engine = Self {
unsafe_to_safe: HashMap::new(),
stats: SecurityFixItStats::new(),
};
engine.init_mappings();
engine
}
fn init_mappings(&mut self) {
self.unsafe_to_safe.insert(
"gets".to_string(),
SafeReplacement {
function: "fgets".to_string(),
extra_args: Some("sizeof(buffer), stdin".to_string()),
description: "gets() is unsafe (buffer overflow). Use fgets().".to_string(),
severity: 10,
},
);
self.unsafe_to_safe.insert(
"strcpy".to_string(),
SafeReplacement {
function: "strncpy".to_string(),
extra_args: Some("dest_size".to_string()),
description: "strcpy() does not check bounds. Use strncpy().".to_string(),
severity: 8,
},
);
self.unsafe_to_safe.insert(
"sprintf".to_string(),
SafeReplacement {
function: "snprintf".to_string(),
extra_args: Some("buf_size".to_string()),
description: "sprintf() does not check bounds. Use snprintf().".to_string(),
severity: 8,
},
);
self.unsafe_to_safe.insert(
"scanf".to_string(),
SafeReplacement {
function: "sscanf".to_string(),
extra_args: Some("(fgets(buf, sizeof(buf), stdin), buf)".to_string()),
description: "scanf() can overflow. Use fgets() + sscanf().".to_string(),
severity: 7,
},
);
self.unsafe_to_safe.insert(
"strcat".to_string(),
SafeReplacement {
function: "strncat".to_string(),
extra_args: Some("dest_size".to_string()),
description: "strcat() does not check bounds. Use strncat().".to_string(),
severity: 8,
},
);
let more_unsafe = [
(
"vsprintf",
"vsnprintf",
"buf_size",
"vsprintf() unsafe. Use vsnprintf().",
8u8,
),
(
"wcscpy",
"wcsncpy",
"dest_size",
"wcscpy() unsafe. Use wcsncpy().",
8u8,
),
(
"wcscat",
"wcsncat",
"dest_size",
"wcscat() unsafe. Use wcsncat().",
8u8,
),
(
"_getws",
"fgetws",
"sizeof(buf), stdin",
"_getws() unsafe. Use fgetws().",
10u8,
),
(
"_getts",
"fgets",
"sizeof(buf), stdin",
"_getts() unsafe. Use fgets().",
10u8,
),
];
for (unsafe_fn, safe_fn, extra, desc, severity) in more_unsafe.iter() {
self.unsafe_to_safe.insert(
unsafe_fn.to_string(),
SafeReplacement {
function: safe_fn.to_string(),
extra_args: Some(extra.to_string()),
description: desc.to_string(),
severity: *severity,
},
);
}
}
pub fn get_safe_replacement(&self, function_name: &str) -> Option<&SafeReplacement> {
self.unsafe_to_safe.get(function_name)
}
pub fn is_unsafe(&self, function_name: &str) -> bool {
self.unsafe_to_safe.contains_key(function_name)
}
pub fn suggest_safe_replacement(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
function_name: &str,
) -> Option<X86FixItHint> {
if let Some(repl) = self.get_safe_replacement(function_name) {
let id = engine.allocate_id();
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
&repl.function,
&repl.description,
X86FixItCategory::UnsafeFunction,
)
.with_meta("severity", &repl.severity.to_string());
match function_name {
"gets" => self.stats.gets_found += 1,
"strcpy" => self.stats.strcpy_found += 1,
"sprintf" => self.stats.sprintf_found += 1,
"scanf" => self.stats.scanf_found += 1,
"strcat" => self.stats.strcat_found += 1,
_ => {}
}
self.stats.unsafe_calls_total += 1;
engine.add_hint(hint);
return engine.hints().last().cloned();
}
None
}
pub fn unsafe_functions(&self) -> Vec<&String> {
self.unsafe_to_safe.keys().collect()
}
pub fn generate_all_hints(
&mut self,
hints: &mut Vec<X86FixItHint>,
sources: &HashMap<String, String>,
) {
for (file, source) in sources {
for (line_idx, line) in source.lines().enumerate() {
let line_num = line_idx + 1;
for (unsafe_fn, repl) in &self.unsafe_to_safe.clone() {
if let Some(pos) = line.find(unsafe_fn.as_str()) {
let after = &line[pos + unsafe_fn.len()..];
let after_trimmed = after.trim_start();
if after_trimmed.starts_with('(') {
let col = pos + 1;
let hint = X86FixItHint::new_replacement(
hints.len() as u64 + 200000,
file,
line_num,
col,
line_num,
col + unsafe_fn.len(),
&repl.function,
&repl.description,
X86FixItCategory::UnsafeFunction,
);
hints.push(hint);
match unsafe_fn.as_str() {
"gets" => self.stats.gets_found += 1,
"strcpy" => self.stats.strcpy_found += 1,
"sprintf" => self.stats.sprintf_found += 1,
"scanf" => self.stats.scanf_found += 1,
"strcat" => self.stats.strcat_found += 1,
_ => {}
}
self.stats.unsafe_calls_total += 1;
}
}
}
}
}
}
pub fn reset_stats(&mut self) {
self.stats.reset();
}
}
impl Default for X86SecurityFixIt {
fn default() -> Self {
Self::new()
}
}
pub struct X86PerformanceFixIt {
enabled_checks: HashSet<PerformanceCheck>,
pub stats: PerformanceFixItStats,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PerformanceCheck {
PassByValue,
PostIncrement,
ReserveBeforePushBack,
EmplaceBack,
MoveSemantics,
LoopUnrolling,
InlineSuggestion,
}
impl fmt::Display for PerformanceCheck {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PassByValue => write!(f, "performance-pass-by-value"),
Self::PostIncrement => write!(f, "performance-post-increment"),
Self::ReserveBeforePushBack => write!(f, "performance-reserve-push-back"),
Self::EmplaceBack => write!(f, "performance-emplace-back"),
Self::MoveSemantics => write!(f, "performance-move-semantics"),
Self::LoopUnrolling => write!(f, "performance-loop-unrolling"),
Self::InlineSuggestion => write!(f, "performance-inline"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct PerformanceFixItStats {
pub pass_by_value: usize,
pub post_increment: usize,
pub reserve_push_back: usize,
pub emplace_back: usize,
pub move_semantics: usize,
pub total: usize,
}
impl PerformanceFixItStats {
pub fn new() -> Self {
Self::default()
}
pub fn reset(&mut self) {
*self = Self::default();
}
}
impl X86PerformanceFixIt {
pub fn new() -> Self {
let mut engine = Self {
enabled_checks: HashSet::new(),
stats: PerformanceFixItStats::new(),
};
engine.enable_all();
engine
}
fn enable_all(&mut self) {
use PerformanceCheck::*;
for check in &[
PassByValue,
PostIncrement,
ReserveBeforePushBack,
EmplaceBack,
MoveSemantics,
LoopUnrolling,
InlineSuggestion,
] {
self.enabled_checks.insert(*check);
}
}
pub fn enable(&mut self, check: PerformanceCheck) {
self.enabled_checks.insert(check);
}
pub fn disable(&mut self, check: PerformanceCheck) {
self.enabled_checks.remove(&check);
}
pub fn is_enabled(&self, check: PerformanceCheck) -> bool {
self.enabled_checks.contains(&check)
}
pub fn suggest_const_reference(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
type_name: &str,
param_name: &str,
) -> Option<X86FixItHint> {
if !self.is_enabled(PerformanceCheck::PassByValue) {
return None;
}
let replacement = format!("const {}& {}", type_name, param_name);
let desc = format!("pass '{}' by const reference to avoid copy", param_name);
let id = engine.allocate_id();
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
&replacement,
&desc,
X86FixItCategory::PassByValue,
);
self.stats.pass_by_value += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_pre_increment(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
) -> Option<X86FixItHint> {
if !self.is_enabled(PerformanceCheck::PostIncrement) {
return None;
}
let id = engine.allocate_id();
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
"++",
"use pre-increment for better performance",
X86FixItCategory::PostIncrement,
);
self.stats.post_increment += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_reserve_before_push_back(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
line: usize,
col: usize,
container: &str,
size_hint: &str,
) -> Option<X86FixItHint> {
if !self.is_enabled(PerformanceCheck::ReserveBeforePushBack) {
return None;
}
let text = format!("{}.reserve({});\n", container, size_hint);
let desc = format!("reserve capacity for '{}' before push_back loop", container);
let id = engine.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
&text,
&desc,
X86FixItCategory::ReserveBeforePushBack,
);
self.stats.reserve_push_back += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_emplace_back(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
) -> Option<X86FixItHint> {
if !self.is_enabled(PerformanceCheck::EmplaceBack) {
return None;
}
let id = engine.allocate_id();
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
"emplace_back",
"use emplace_back to construct in-place",
X86FixItCategory::EmplaceBack,
);
self.stats.emplace_back += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_move(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
line: usize,
col: usize,
variable: &str,
) -> Option<X86FixItHint> {
if !self.is_enabled(PerformanceCheck::MoveSemantics) {
return None;
}
let text = format!("std::move({})", variable);
let id = engine.allocate_id();
let hint = X86FixItHint::new_replacement(
id,
file,
line,
col,
line,
col + variable.len(),
&text,
"use std::move to enable move semantics",
X86FixItCategory::MoveSemantics,
);
self.stats.move_semantics += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn generate_all_hints(
&mut self,
_hints: &mut Vec<X86FixItHint>,
sources: &HashMap<String, String>,
) {
for (_file, source) in sources {
if self.is_enabled(PerformanceCheck::PostIncrement) {
if source.contains("++") {
self.stats.post_increment += 1;
}
}
if self.is_enabled(PerformanceCheck::ReserveBeforePushBack) {
if source.contains("push_back") && !source.contains("reserve") {
self.stats.reserve_push_back += 1;
}
}
if self.is_enabled(PerformanceCheck::PassByValue) {
self.stats.pass_by_value += 1;
}
}
self.stats.total = self.stats.pass_by_value
+ self.stats.post_increment
+ self.stats.reserve_push_back
+ self.stats.emplace_back
+ self.stats.move_semantics;
}
pub fn reset_stats(&mut self) {
self.stats.reset();
}
}
impl Default for X86PerformanceFixIt {
fn default() -> Self {
Self::new()
}
}
pub struct X86StyleFixIt {
brace_style: BraceStyle,
naming_convention: NamingConvention,
indent_width: usize,
use_tabs: bool,
max_line_length: usize,
enabled_checks: HashSet<StyleCheck>,
pub stats: StyleFixItStats,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BraceStyle {
Attached,
NewLine,
Gnu,
Linux,
}
impl fmt::Display for BraceStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Attached => write!(f, "attached"),
Self::NewLine => write!(f, "newline"),
Self::Gnu => write!(f, "gnu"),
Self::Linux => write!(f, "linux"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NamingConvention {
SnakeCase,
CamelCase,
PascalCase,
KebabCase,
UpperCase,
LeadingUnderscore,
TrailingUnderscore,
MPrefix,
}
impl fmt::Display for NamingConvention {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SnakeCase => write!(f, "snake_case"),
Self::CamelCase => write!(f, "camelCase"),
Self::PascalCase => write!(f, "PascalCase"),
Self::KebabCase => write!(f, "kebab-case"),
Self::UpperCase => write!(f, "UPPER_CASE"),
Self::LeadingUnderscore => write!(f, "_leading_underscore"),
Self::TrailingUnderscore => write!(f, "trailing_underscore_"),
Self::MPrefix => write!(f, "m_ prefixed"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StyleCheck {
BraceStyle,
NamingConvention,
Indentation,
TrailingWhitespace,
LineLength,
}
impl fmt::Display for StyleCheck {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BraceStyle => write!(f, "style-brace-style"),
Self::NamingConvention => write!(f, "style-naming"),
Self::Indentation => write!(f, "style-indentation"),
Self::TrailingWhitespace => write!(f, "style-trailing-whitespace"),
Self::LineLength => write!(f, "style-line-length"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct StyleFixItStats {
pub brace_corrections: usize,
pub naming_corrections: usize,
pub indent_corrections: usize,
pub trailing_whitespace: usize,
pub line_length: usize,
pub total: usize,
}
impl StyleFixItStats {
pub fn new() -> Self {
Self::default()
}
pub fn reset(&mut self) {
*self = Self::default();
}
}
impl X86StyleFixIt {
pub fn new() -> Self {
let mut engine = Self {
brace_style: BraceStyle::Attached,
naming_convention: NamingConvention::PascalCase,
indent_width: 2,
use_tabs: false,
max_line_length: 80,
enabled_checks: HashSet::new(),
stats: StyleFixItStats::new(),
};
engine.enable_all();
engine
}
fn enable_all(&mut self) {
use StyleCheck::*;
for check in &[
BraceStyle,
NamingConvention,
Indentation,
TrailingWhitespace,
LineLength,
] {
self.enabled_checks.insert(*check);
}
}
pub fn set_brace_style(&mut self, style: BraceStyle) {
self.brace_style = style;
}
pub fn brace_style(&self) -> BraceStyle {
self.brace_style
}
pub fn set_naming_convention(&mut self, convention: NamingConvention) {
self.naming_convention = convention;
}
pub fn set_indent_width(&mut self, width: usize) {
self.indent_width = width.clamp(1, 16);
}
pub fn set_use_tabs(&mut self, use_tabs: bool) {
self.use_tabs = use_tabs;
}
pub fn set_max_line_length(&mut self, length: usize) {
self.max_line_length = length.clamp(40, 200);
}
pub fn enable(&mut self, check: StyleCheck) {
self.enabled_checks.insert(check);
}
pub fn disable(&mut self, check: StyleCheck) {
self.enabled_checks.remove(&check);
}
pub fn is_enabled(&self, check: StyleCheck) -> bool {
self.enabled_checks.contains(&check)
}
pub fn suggest_attached_brace(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
line: usize,
col: usize,
_prev_end: usize,
) -> Option<X86FixItHint> {
if !self.is_enabled(StyleCheck::BraceStyle) {
return None;
}
let id = engine.allocate_id();
let hint = X86FixItHint::new_replacement(
id,
file,
line,
1,
line,
col + 1,
" {",
"move opening brace to same line",
X86FixItCategory::BraceStyle,
);
self.stats.brace_corrections += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_rename(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
new_name: &str,
) -> Option<X86FixItHint> {
if !self.is_enabled(StyleCheck::NamingConvention) {
return None;
}
let desc = format!(
"rename to '{}' ({} convention)",
new_name, self.naming_convention
);
let id = engine.allocate_id();
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
new_name,
&desc,
X86FixItCategory::NamingConvention,
);
self.stats.naming_corrections += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn convert_name(&self, name: &str) -> String {
match self.naming_convention {
NamingConvention::SnakeCase => Self::to_snake_case(name),
NamingConvention::PascalCase => Self::to_pascal_case(name),
NamingConvention::CamelCase => Self::to_camel_case(name),
NamingConvention::UpperCase => name.to_uppercase(),
NamingConvention::MPrefix => format!("m_{}", Self::to_camel_case(name)),
_ => name.to_string(),
}
}
fn to_snake_case(name: &str) -> String {
let mut result = String::new();
for (i, c) in name.chars().enumerate() {
if c.is_uppercase() {
if i > 0 {
result.push('_');
}
result.push(c.to_lowercase().next().unwrap_or(c));
} else {
result.push(c);
}
}
result
}
fn to_pascal_case(name: &str) -> String {
let mut result = String::new();
let mut capitalize = true;
for c in name.chars() {
if c == '_' {
capitalize = true;
} else if capitalize {
result.push(c.to_uppercase().next().unwrap_or(c));
capitalize = false;
} else {
result.push(c);
}
}
result
}
fn to_camel_case(name: &str) -> String {
let pascal = Self::to_pascal_case(name);
if pascal.is_empty() {
return pascal;
}
let mut chars = pascal.chars();
let first = chars.next().unwrap().to_lowercase().next().unwrap_or('_');
let rest: String = chars.collect();
format!("{}{}", first, rest)
}
pub fn suggest_indent_correction(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
line: usize,
expected_indent_level: usize,
) -> Option<X86FixItHint> {
if !self.is_enabled(StyleCheck::Indentation) {
return None;
}
let indent_str = if self.use_tabs {
"\t".repeat(expected_indent_level)
} else {
" ".repeat(expected_indent_level * self.indent_width)
};
let desc = format!(
"correct indentation to {} spaces",
expected_indent_level * self.indent_width
);
let id = engine.allocate_id();
let hint = X86FixItHint::new_replacement(
id,
file,
line,
1,
line,
1,
&indent_str,
&desc,
X86FixItCategory::Indentation,
);
self.stats.indent_corrections += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_remove_trailing_whitespace(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
if !self.is_enabled(StyleCheck::TrailingWhitespace) {
return None;
}
let id = engine.allocate_id();
let hint = X86FixItHint::new_removal(
id,
file,
line,
col,
line,
999,
"remove trailing whitespace",
X86FixItCategory::TrailingWhitespace,
);
self.stats.trailing_whitespace += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_line_break(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
if !self.is_enabled(StyleCheck::LineLength) {
return None;
}
let id = engine.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
"\n",
"insert line break for readability",
X86FixItCategory::LineLength,
);
self.stats.line_length += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn generate_all_hints(
&mut self,
_hints: &mut Vec<X86FixItHint>,
sources: &HashMap<String, String>,
) {
for (_file, source) in sources {
for line in source.lines() {
if self.is_enabled(StyleCheck::TrailingWhitespace) {
let trimmed = line.trim_end();
if trimmed.len() < line.len() {
self.stats.trailing_whitespace += 1;
}
}
if self.is_enabled(StyleCheck::LineLength) {
if line.len() > self.max_line_length {
self.stats.line_length += 1;
}
}
}
}
self.stats.total = self.stats.brace_corrections
+ self.stats.naming_corrections
+ self.stats.indent_corrections
+ self.stats.trailing_whitespace
+ self.stats.line_length;
}
pub fn reset_stats(&mut self) {
self.stats.reset();
}
}
impl Default for X86StyleFixIt {
fn default() -> Self {
Self::new()
}
}
pub struct X86PortabilityFixIt {
data_model: X86DataModel,
enabled_checks: HashSet<PortabilityCheck>,
pub stats: PortabilityFixItStats,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DataModel {
ILP32,
LP64,
LLP64,
ILP64,
}
impl Default for X86DataModel {
fn default() -> Self {
Self::LP64
}
}
impl fmt::Display for X86DataModel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ILP32 => write!(f, "ILP32"),
Self::LP64 => write!(f, "LP64"),
Self::LLP64 => write!(f, "LLP64"),
Self::ILP64 => write!(f, "ILP64"),
}
}
}
impl X86DataModel {
pub fn sizeof_long(&self) -> usize {
match self {
Self::ILP32 => 4,
Self::LP64 => 8,
Self::LLP64 => 4,
Self::ILP64 => 8,
}
}
pub fn sizeof_pointer(&self) -> usize {
match self {
Self::ILP32 => 4,
Self::LP64 => 8,
Self::LLP64 => 8,
Self::ILP64 => 8,
}
}
pub fn sizeof_size_t(&self) -> usize {
self.sizeof_pointer()
}
pub fn size_t_format(&self) -> &'static str {
match self {
Self::ILP32 => "%u",
Self::LP64 => "%zu",
Self::LLP64 => "%Iu", Self::ILP64 => "%zu",
}
}
pub fn ptrdiff_t_format(&self) -> &'static str {
match self {
Self::ILP32 => "%d",
Self::LP64 => "%zd",
Self::LLP64 => "%Id",
Self::ILP64 => "%zd",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PortabilityCheck {
SizeTFormat,
LongWidthDifferences,
StructPacking,
EndianDependent,
AlignmentPortability,
X86Intrinsics,
}
impl fmt::Display for PortabilityCheck {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SizeTFormat => write!(f, "portability-size-t-format"),
Self::LongWidthDifferences => write!(f, "portability-long-width"),
Self::StructPacking => write!(f, "portability-struct-packing"),
Self::EndianDependent => write!(f, "portability-endian"),
Self::AlignmentPortability => write!(f, "portability-alignment"),
Self::X86Intrinsics => write!(f, "portability-x86-intrinsics"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct PortabilityFixItStats {
pub size_t_format: usize,
pub long_width: usize,
pub struct_packing: usize,
pub endian: usize,
pub alignment: usize,
pub x86_intrinsics: usize,
pub total: usize,
}
impl PortabilityFixItStats {
pub fn new() -> Self {
Self::default()
}
pub fn reset(&mut self) {
*self = Self::default();
}
}
impl X86PortabilityFixIt {
pub fn new() -> Self {
let mut engine = Self {
data_model: X86DataModel::default(),
enabled_checks: HashSet::new(),
stats: PortabilityFixItStats::new(),
};
engine.enable_all();
engine
}
fn enable_all(&mut self) {
use PortabilityCheck::*;
for check in &[
SizeTFormat,
LongWidthDifferences,
StructPacking,
EndianDependent,
AlignmentPortability,
X86Intrinsics,
] {
self.enabled_checks.insert(*check);
}
}
pub fn set_data_model(&mut self, model: X86DataModel) {
self.data_model = model;
}
pub fn data_model(&self) -> X86DataModel {
self.data_model
}
pub fn enable(&mut self, check: PortabilityCheck) {
self.enabled_checks.insert(check);
}
pub fn disable(&mut self, check: PortabilityCheck) {
self.enabled_checks.remove(&check);
}
pub fn is_enabled(&self, check: PortabilityCheck) -> bool {
self.enabled_checks.contains(&check)
}
pub fn suggest_size_t_format(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
) -> Option<X86FixItHint> {
if !self.is_enabled(PortabilityCheck::SizeTFormat) {
return None;
}
let format = self.data_model.size_t_format();
let desc = format!(
"use '{}' format specifier for size_t on {}",
format, self.data_model
);
let id = engine.allocate_id();
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
format,
&desc,
X86FixItCategory::SizeTFormat,
);
self.stats.size_t_format += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_fixed_width_type(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
start_line: usize,
start_col: usize,
end_line: usize,
end_col: usize,
fixed_type: &str,
) -> Option<X86FixItHint> {
if !self.is_enabled(PortabilityCheck::LongWidthDifferences) {
return None;
}
let desc = format!(
"use '{}' instead of 'long' for portability (LP64: sizeof(long)=8, LLP64: sizeof(long)=4)",
fixed_type
);
let id = engine.allocate_id();
let hint = X86FixItHint::new_replacement(
id,
file,
start_line,
start_col,
end_line,
end_col,
fixed_type,
&desc,
X86FixItCategory::LongWidthDifference,
);
self.stats.long_width += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_packed_attribute(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
line: usize,
col: usize,
) -> Option<X86FixItHint> {
if !self.is_enabled(PortabilityCheck::StructPacking) {
return None;
}
let id = engine.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
col,
" __attribute__((packed))",
"add __attribute__((packed)) for consistent struct layout across platforms",
X86FixItCategory::StructPacking,
);
self.stats.struct_packing += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn suggest_pragma_pack(
&mut self,
engine: &mut X86FixItEngine,
file: &str,
line: usize,
align: usize,
) -> Option<X86FixItHint> {
if !self.is_enabled(PortabilityCheck::StructPacking) {
return None;
}
let text = format!("#pragma pack({})\n", align);
let desc = format!("add #pragma pack({}) for portable struct packing", align);
let id = engine.allocate_id();
let hint = X86FixItHint::new_insertion(
id,
file,
line,
1,
&text,
&desc,
X86FixItCategory::StructPacking,
);
self.stats.struct_packing += 1;
engine.add_hint(hint);
engine.hints().last().cloned()
}
pub fn generate_all_hints(
&mut self,
_hints: &mut Vec<X86FixItHint>,
sources: &HashMap<String, String>,
) {
for (_file, source) in sources {
if self.is_enabled(PortabilityCheck::SizeTFormat) {
if source.contains("size_t") && source.contains("printf") {
if source.contains("%d") || source.contains("%u") || source.contains("%ld") {
self.stats.size_t_format += 1;
}
}
}
if self.is_enabled(PortabilityCheck::LongWidthDifferences) {
if source.contains("long ") || source.contains("long\n") {
self.stats.long_width += 1;
}
}
if self.is_enabled(PortabilityCheck::StructPacking) {
if source.contains("struct ") && !source.contains("__attribute__((packed))") {
self.stats.struct_packing += 1;
}
}
if self.is_enabled(PortabilityCheck::AlignmentPortability) {
if source.contains("alignof") || source.contains("__alignof__") {
self.stats.alignment += 1;
}
}
}
self.stats.total = self.stats.size_t_format
+ self.stats.long_width
+ self.stats.struct_packing
+ self.stats.endian
+ self.stats.alignment
+ self.stats.x86_intrinsics;
}
pub fn reset_stats(&mut self) {
self.stats.reset();
}
}
impl Default for X86PortabilityFixIt {
fn default() -> Self {
Self::new()
}
}
pub struct X86FixItConflictResolver {
conflicts: Vec<Vec<usize>>,
strategy: ConflictResolutionStrategy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConflictResolutionStrategy {
KeepHighestConfidence,
KeepHighestPriority,
KeepFirst,
KeepLast,
Merge,
Prompt,
}
impl Default for ConflictResolutionStrategy {
fn default() -> Self {
Self::KeepHighestConfidence
}
}
impl X86FixItConflictResolver {
pub fn new() -> Self {
Self {
conflicts: Vec::new(),
strategy: ConflictResolutionStrategy::default(),
}
}
pub fn set_strategy(&mut self, strategy: ConflictResolutionStrategy) {
self.strategy = strategy;
}
pub fn detect_conflicts(&mut self, hints: &[X86FixItHint]) -> Vec<Vec<usize>> {
self.conflicts.clear();
let n = hints.len();
if n <= 1 {
return Vec::new();
}
let mut graph: Vec<Vec<usize>> = vec![Vec::new(); n];
for i in 0..n {
for j in i + 1..n {
if hints_overlap(&hints[i], &hints[j]) {
graph[i].push(j);
graph[j].push(i);
}
}
}
let mut visited = vec![false; n];
for i in 0..n {
if !visited[i] && !graph[i].is_empty() {
let mut component = Vec::new();
let mut stack = vec![i];
while let Some(node) = stack.pop() {
if !visited[node] {
visited[node] = true;
component.push(node);
for &neighbor in &graph[node] {
if !visited[neighbor] {
stack.push(neighbor);
}
}
}
}
if component.len() > 1 {
self.conflicts.push(component);
}
}
}
self.conflicts.clone()
}
pub fn resolve(&self, hints: &[X86FixItHint]) -> Vec<usize> {
let mut kept: Vec<usize> = (0..hints.len()).collect();
for conflict_group in &self.conflicts {
if conflict_group.is_empty() {
continue;
}
let keep_idx = match self.strategy {
ConflictResolutionStrategy::KeepHighestConfidence => *conflict_group
.iter()
.max_by(|&&a, &&b| {
hints[a]
.confidence
.partial_cmp(&hints[b].confidence)
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap_or(&conflict_group[0]),
ConflictResolutionStrategy::KeepHighestPriority => *conflict_group
.iter()
.min_by_key(|&&idx| hints[idx].category.priority())
.unwrap_or(&conflict_group[0]),
ConflictResolutionStrategy::KeepFirst => conflict_group[0],
ConflictResolutionStrategy::KeepLast => {
*conflict_group.last().unwrap_or(&conflict_group[0])
}
ConflictResolutionStrategy::Merge | ConflictResolutionStrategy::Prompt => {
conflict_group[0] }
};
for &idx in conflict_group {
if idx != keep_idx {
kept.retain(|&k| k != idx);
}
}
}
kept
}
pub fn has_overlap(hint1: &X86FixItHint, hint2: &X86FixItHint) -> bool {
if hint1.file != hint2.file {
return false;
}
hints_overlap(hint1, hint2)
}
}
impl Default for X86FixItConflictResolver {
fn default() -> Self {
Self::new()
}
}
fn hints_overlap(a: &X86FixItHint, b: &X86FixItHint) -> bool {
if a.file != b.file {
return false;
}
if a.start_line > b.end_line || b.start_line > a.end_line {
return false;
}
if a.start_line == b.end_line && a.start_col > b.end_col {
return false;
}
if b.start_line == a.end_line && b.start_col > a.end_col {
return false;
}
true
}
pub struct X86FixItApplicator {
pub applied: Vec<X86FixItHint>,
pub rejected: Vec<(X86FixItHint, String)>,
pub resolver: X86FixItConflictResolver,
}
impl X86FixItApplicator {
pub fn new() -> Self {
Self {
applied: Vec::new(),
rejected: Vec::new(),
resolver: X86FixItConflictResolver::new(),
}
}
pub fn apply_one(&mut self, hint: &X86FixItHint, source: &str) -> Result<String, String> {
match hint.kind {
X86FixItKind::Replacement => self.apply_replacement(hint, source),
X86FixItKind::Insertion => self.apply_insertion(hint, source),
X86FixItKind::Removal => self.apply_removal(hint, source),
X86FixItKind::Composition => self.apply_composition(hint, source),
}
}
fn apply_replacement(&mut self, hint: &X86FixItHint, source: &str) -> Result<String, String> {
let lines: Vec<&str> = source.lines().collect();
if hint.start_line == 0 || hint.start_line > lines.len() {
self.rejected
.push((hint.clone(), "invalid line range".into()));
return Err("invalid line range".into());
}
if hint.start_line == hint.end_line {
let line = lines[hint.start_line - 1];
if hint.start_col == 0 || hint.start_col > line.len() + 1 {
self.rejected.push((hint.clone(), "invalid column".into()));
return Err("invalid column".into());
}
let before = &line[..hint.start_col - 1];
let after = if hint.end_col <= line.len() {
&line[hint.end_col - 1..]
} else {
""
};
let new_line = format!("{}{}{}", before, hint.text, after);
let mut result = String::new();
for (i, l) in lines.iter().enumerate() {
if i + 1 == hint.start_line {
result.push_str(&new_line);
} else {
result.push_str(l);
}
if i + 1 < lines.len() {
result.push('\n');
}
}
self.applied.push(hint.clone());
Ok(result)
} else {
self.rejected
.push((hint.clone(), "multi-line not supported".into()));
Err("multi-line not supported".into())
}
}
fn apply_insertion(&mut self, hint: &X86FixItHint, source: &str) -> Result<String, String> {
let lines: Vec<&str> = source.lines().collect();
if hint.start_line == 0 || hint.start_line > lines.len() {
self.rejected
.push((hint.clone(), "invalid insertion line".into()));
return Err("invalid insertion line".into());
}
let line = lines[hint.start_line - 1];
let insert_pos = if hint.start_col == 0 || hint.start_col > line.len() + 1 {
line.len()
} else {
hint.start_col - 1
};
let before = &line[..insert_pos];
let after = &line[insert_pos..];
let new_line = format!("{}{}{}", before, hint.text, after);
let mut result = String::new();
for (i, l) in lines.iter().enumerate() {
if i + 1 == hint.start_line {
result.push_str(&new_line);
} else {
result.push_str(l);
}
if i + 1 < lines.len() {
result.push('\n');
}
}
self.applied.push(hint.clone());
Ok(result)
}
fn apply_removal(&mut self, hint: &X86FixItHint, source: &str) -> Result<String, String> {
let lines: Vec<&str> = source.lines().collect();
if hint.start_line == 0 || hint.start_line > lines.len() {
self.rejected
.push((hint.clone(), "invalid line range for removal".into()));
return Err("invalid line range for removal".into());
}
if hint.start_line == hint.end_line {
let line = lines[hint.start_line - 1];
if hint.start_col == 0 || hint.start_col > line.len() + 1 {
self.rejected
.push((hint.clone(), "invalid column for removal".into()));
return Err("invalid column for removal".into());
}
let before = &line[..hint.start_col - 1];
let end = min(hint.end_col, line.len() + 1);
let after = if end <= line.len() {
&line[end - 1..]
} else {
""
};
let new_line = format!("{}{}", before, after);
let mut result = String::new();
for (i, l) in lines.iter().enumerate() {
if i + 1 == hint.start_line {
result.push_str(&new_line);
} else {
result.push_str(l);
}
if i + 1 < lines.len() {
result.push('\n');
}
}
self.applied.push(hint.clone());
Ok(result)
} else {
self.rejected
.push((hint.clone(), "multi-line removal not supported".into()));
Err("multi-line removal not supported".into())
}
}
fn apply_composition(&mut self, hint: &X86FixItHint, source: &str) -> Result<String, String> {
let mut current = source.to_string();
for sub in &hint.sub_hints {
match sub.kind {
X86FixItKind::Replacement => {
current = self.apply_replacement(sub, ¤t)?;
}
X86FixItKind::Insertion => {
current = self.apply_insertion(sub, ¤t)?;
}
X86FixItKind::Removal => {
current = self.apply_removal(sub, ¤t)?;
}
X86FixItKind::Composition => {
current = self.apply_composition(sub, ¤t)?;
}
}
}
self.applied.push(hint.clone());
Ok(current)
}
pub fn apply_all(&mut self, hints: &[X86FixItHint], source: &str) -> String {
let conflicts = self.resolver.detect_conflicts(hints);
let keep_indices = if conflicts.is_empty() {
(0..hints.len()).collect()
} else {
self.resolver.resolve(hints)
};
let mut current = source.to_string();
let mut to_apply: Vec<&X86FixItHint> = keep_indices.iter().map(|&i| &hints[i]).collect();
to_apply.sort_by_key(|h| (h.start_line, h.start_col));
to_apply.reverse();
for hint in &to_apply {
if let Ok(new_source) = self.apply_one(hint, ¤t) {
current = new_source;
}
}
current
}
pub fn applied_count(&self) -> usize {
self.applied.len()
}
pub fn rejected_count(&self) -> usize {
self.rejected.len()
}
pub fn clear(&mut self) {
self.applied.clear();
self.rejected.clear();
}
}
impl Default for X86FixItApplicator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86FixItDiff {
pub file: String,
pub hunks: Vec<X86DiffHunk>,
}
#[derive(Debug, Clone)]
pub struct X86DiffHunk {
pub old_start: usize,
pub old_count: usize,
pub new_start: usize,
pub new_count: usize,
pub lines: Vec<X86DiffLine>,
}
#[derive(Debug, Clone)]
pub enum X86DiffLine {
Context(String),
Addition(String),
Removal(String),
}
impl fmt::Display for X86DiffLine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Context(line) => write!(f, " {}", line),
Self::Addition(line) => write!(f, "+ {}", line),
Self::Removal(line) => write!(f, "- {}", line),
}
}
}
impl X86FixItDiff {
pub fn compute(original: &str, modified: &str, file: &str) -> Self {
let orig_lines: Vec<&str> = original.lines().collect();
let mod_lines: Vec<&str> = modified.lines().collect();
let mut hunks = Vec::new();
let mut i = 0;
let mut j = 0;
while i < orig_lines.len() || j < mod_lines.len() {
if i < orig_lines.len() && j < mod_lines.len() && orig_lines[i] == mod_lines[j] {
let hunk = X86DiffHunk {
old_start: i + 1,
old_count: 1,
new_start: j + 1,
new_count: 1,
lines: vec![X86DiffLine::Context(orig_lines[i].to_string())],
};
hunks.push(hunk);
i += 1;
j += 1;
} else if i < orig_lines.len()
&& (j >= mod_lines.len() || orig_lines[i] != mod_lines[j])
{
let hunk = X86DiffHunk {
old_start: i + 1,
old_count: 1,
new_start: j + 1,
new_count: 0,
lines: vec![X86DiffLine::Removal(orig_lines[i].to_string())],
};
hunks.push(hunk);
i += 1;
if j < mod_lines.len() {
let add_hunk = X86DiffHunk {
old_start: i,
old_count: 0,
new_start: j + 1,
new_count: 1,
lines: vec![X86DiffLine::Addition(mod_lines[j].to_string())],
};
hunks.push(add_hunk);
j += 1;
}
} else if j < mod_lines.len() {
let hunk = X86DiffHunk {
old_start: i + 1,
old_count: 0,
new_start: j + 1,
new_count: 1,
lines: vec![X86DiffLine::Addition(mod_lines[j].to_string())],
};
hunks.push(hunk);
j += 1;
}
}
X86FixItDiff {
file: file.to_string(),
hunks,
}
}
pub fn render(&self) -> String {
let mut output = String::new();
output.push_str(&format!("--- a/{}\n", self.file));
output.push_str(&format!("+++ b/{}\n", self.file));
let mut in_hunk = false;
let mut hunk_lines: Vec<&X86DiffLine> = Vec::new();
let mut old_start = 0;
let mut new_start = 0;
for hunk in &self.hunks {
if hunk.lines.is_empty() {
continue;
}
let has_changes = hunk
.lines
.iter()
.any(|l| !matches!(l, X86DiffLine::Context(_)));
if has_changes {
if !in_hunk {
old_start = hunk.old_start;
new_start = hunk.new_start;
in_hunk = true;
}
hunk_lines.extend(&hunk.lines);
} else if in_hunk {
hunk_lines.extend(&hunk.lines);
}
}
if in_hunk && hunk_lines.len() > 1 {
let old_count = hunk_lines
.iter()
.filter(|l| !matches!(l, X86DiffLine::Addition(_)))
.count();
let new_count = hunk_lines
.iter()
.filter(|l| !matches!(l, X86DiffLine::Removal(_)))
.count();
output.push_str(&format!(
"@@ -{},{} +{},{} @@\n",
old_start, old_count, new_start, new_count
));
for line in &hunk_lines {
output.push_str(&format!("{}\n", line));
}
}
output
}
}
pub struct X86FixItValidator {
pub validated: Vec<u64>,
pub failed: Vec<(u64, String)>,
}
impl X86FixItValidator {
pub fn new() -> Self {
Self {
validated: Vec::new(),
failed: Vec::new(),
}
}
pub fn validate(&mut self, hint: &X86FixItHint, sources: &HashMap<String, String>) -> bool {
if !sources.contains_key(&hint.file) {
self.failed
.push((hint.id, format!("file '{}' not found", hint.file)));
return false;
}
let source = &sources[&hint.file];
let lines: Vec<&str> = source.lines().collect();
if hint.start_line == 0 || hint.start_line > lines.len() {
self.failed.push((
hint.id,
format!("start line {} out of range", hint.start_line),
));
return false;
}
if hint.end_line == 0 || hint.end_line > lines.len() {
self.failed
.push((hint.id, format!("end line {} out of range", hint.end_line)));
return false;
}
if hint.is_single_line() {
let line = lines[hint.start_line - 1];
if hint.start_col == 0 || hint.start_col > line.len() + 1 {
self.failed.push((
hint.id,
format!(
"start column {} out of range (line len={})",
hint.start_col,
line.len()
),
));
return false;
}
match hint.kind {
X86FixItKind::Replacement => {
if hint.end_col < hint.start_col {
self.failed
.push((hint.id, "end column before start column".into()));
return false;
}
}
X86FixItKind::Removal => {
if hint.end_col < hint.start_col {
self.failed
.push((hint.id, "end column before start column".into()));
return false;
}
}
_ => {}
}
}
if let X86FixItKind::Composition = hint.kind {
if hint.sub_hints.is_empty() {
self.failed
.push((hint.id, "composition has no sub-hints".into()));
return false;
}
for sub in &hint.sub_hints {
if !self.validate(sub, sources) {
self.failed
.push((hint.id, format!("sub-hint {} validation failed", sub.id)));
return false;
}
}
}
if hint.confidence < 0.0 || hint.confidence > 1.0 {
self.failed
.push((hint.id, format!("invalid confidence {}", hint.confidence)));
return false;
}
self.validated.push(hint.id);
true
}
pub fn validate_all(
&mut self,
hints: &[X86FixItHint],
sources: &HashMap<String, String>,
) -> Vec<u64> {
self.validated.clear();
self.failed.clear();
let mut valid_ids = Vec::new();
for hint in hints {
if self.validate(hint, sources) {
valid_ids.push(hint.id);
}
}
valid_ids
}
pub fn validated_count(&self) -> usize {
self.validated.len()
}
pub fn failed_count(&self) -> usize {
self.failed.len()
}
pub fn clear(&mut self) {
self.validated.clear();
self.failed.clear();
}
}
impl Default for X86FixItValidator {
fn default() -> Self {
Self::new()
}
}
pub struct X86FixItPipeline {
pub engine: X86FixItEngine,
pub applicator: X86FixItApplicator,
pub validator: X86FixItValidator,
}
impl X86FixItPipeline {
pub fn new() -> Self {
Self {
engine: X86FixItEngine::new(),
applicator: X86FixItApplicator::new(),
validator: X86FixItValidator::new(),
}
}
pub fn add_source(&mut self, file: &str, content: &str) {
self.engine.add_source(file, content);
}
pub fn run(&mut self, file: &str) -> Result<PipelineResult, String> {
let source = self
.engine
.get_source(file)
.ok_or_else(|| format!("file '{}' not registered", file))?
.to_string();
self.engine.generate_all();
let mut sources_map = HashMap::new();
sources_map.insert(file.to_string(), source.clone());
let valid_ids = self
.validator
.validate_all(self.engine.hints(), &sources_map);
let valid_hints: Vec<&X86FixItHint> = self
.engine
.hints()
.iter()
.filter(|h| valid_ids.contains(&h.id) && h.enabled)
.collect();
let modified = self.applicator.apply_all(
&valid_hints.iter().map(|h| (*h).clone()).collect::<Vec<_>>(),
&source,
);
let diff = X86FixItDiff::compute(&source, &modified, file);
Ok(PipelineResult {
file: file.to_string(),
original: source,
modified,
diff,
hints_generated: self.engine.hints().len(),
hints_applied: self.applicator.applied_count(),
hints_rejected: self.applicator.rejected_count(),
validation_errors: self.validator.failed.len(),
})
}
pub fn preview(&mut self, file: &str) -> Result<String, String> {
let result = self.run(file)?;
let mut preview = String::new();
preview.push_str(&format!("File: {}\n", file));
preview.push_str(&format!("Hints generated: {}\n", result.hints_generated));
preview.push_str(&format!("Hints applied: {}\n", result.hints_applied));
preview.push_str(&format!("Hints rejected: {}\n", result.hints_rejected));
preview.push_str(&format!(
"Validation errors: {}\n",
result.validation_errors
));
preview.push_str("\n--- Diff Preview ---\n");
preview.push_str(&result.diff.render());
Ok(preview)
}
pub fn reset(&mut self) {
self.engine.reset();
self.applicator.clear();
self.validator.clear();
}
}
impl Default for X86FixItPipeline {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PipelineResult {
pub file: String,
pub original: String,
pub modified: String,
pub diff: X86FixItDiff,
pub hints_generated: usize,
pub hints_applied: usize,
pub hints_rejected: usize,
pub validation_errors: usize,
}
#[derive(Debug, Clone, Default)]
pub struct X86FixItMetrics {
pub total_hints: usize,
pub high_confidence: usize,
pub low_confidence: usize,
pub applied: usize,
pub rejected: usize,
pub validation_failures: usize,
pub conflicts: usize,
pub average_confidence: f64,
pub category_distribution: HashMap<X86FixItCategory, usize>,
pub average_edit_distance: f64,
}
impl X86FixItMetrics {
pub fn new() -> Self {
Self::default()
}
pub fn compute(hints: &[X86FixItHint], applied_count: usize, rejected_count: usize) -> Self {
let total = hints.len();
let high_conf = hints.iter().filter(|h| h.confidence >= 0.9).count();
let low_conf = hints.iter().filter(|h| h.confidence < 0.5).count();
let avg_conf = if total > 0 {
hints.iter().map(|h| h.confidence).sum::<f64>() / total as f64
} else {
0.0
};
let mut category_dist: HashMap<X86FixItCategory, usize> = HashMap::new();
for hint in hints {
*category_dist.entry(hint.category).or_insert(0) += 1;
}
Self {
total_hints: total,
high_confidence: high_conf,
low_confidence: low_conf,
applied: applied_count,
rejected: rejected_count,
validation_failures: 0,
conflicts: 0,
average_confidence: avg_conf,
category_distribution: category_dist,
average_edit_distance: 0.0,
}
}
pub fn display(&self) -> String {
let mut s = String::new();
s.push_str(&format!("Total hints: {}\n", self.total_hints));
s.push_str(&format!(
"High confidence: {} ({:.1}%)\n",
self.high_confidence,
if self.total_hints > 0 {
100.0 * self.high_confidence as f64 / self.total_hints as f64
} else {
0.0
}
));
s.push_str(&format!(
"Low confidence: {} ({:.1}%)\n",
self.low_confidence,
if self.total_hints > 0 {
100.0 * self.low_confidence as f64 / self.total_hints as f64
} else {
0.0
}
));
s.push_str(&format!(
"Average confidence: {:.2}\n",
self.average_confidence
));
s.push_str(&format!(
"Applied: {}, Rejected: {}\n",
self.applied, self.rejected
));
s.push_str(&format!(
"Validation failures: {}\n",
self.validation_failures
));
s.push_str(&format!("Conflicts: {}\n", self.conflicts));
s.push_str("Category distribution:\n");
let mut cats: Vec<(&X86FixItCategory, &usize)> =
self.category_distribution.iter().collect();
cats.sort_by_key(|&(_, &count)| std::cmp::Reverse(count));
for (cat, count) in cats {
s.push_str(&format!(" {}: {}\n", cat, count));
}
s
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_fixit_hint_new_replacement() {
let hint = X86FixItHint::new_replacement(
1,
"test.c",
10,
5,
10,
9,
"foo",
"fix identifier",
X86FixItCategory::TypoCorrection,
);
assert_eq!(hint.id, 1);
assert_eq!(hint.file, "test.c");
assert_eq!(hint.start_line, 10);
assert_eq!(hint.start_col, 5);
assert_eq!(hint.end_line, 10);
assert_eq!(hint.end_col, 9);
assert_eq!(hint.text, "foo");
assert_eq!(hint.description, "fix identifier");
assert_eq!(hint.category, X86FixItCategory::TypoCorrection);
assert_eq!(hint.kind, X86FixItKind::Replacement);
assert_eq!(hint.confidence, 1.0);
assert!(hint.enabled);
assert!(hint.is_high_confidence());
assert!(hint.is_single_line());
assert!(hint.has_text());
}
#[test]
fn test_fixit_hint_new_insertion() {
let hint = X86FixItHint::new_insertion(
2,
"test.c",
5,
1,
";",
"insert semicolon",
X86FixItCategory::MissingSemicolon,
);
assert_eq!(hint.kind, X86FixItKind::Insertion);
assert_eq!(hint.text, ";");
assert_eq!(hint.start_line, 5);
assert_eq!(hint.start_col, 1);
}
#[test]
fn test_fixit_hint_new_removal() {
let hint = X86FixItHint::new_removal(
3,
"test.c",
8,
12,
8,
13,
"remove extra semicolon",
X86FixItCategory::ExtraSemicolon,
);
assert_eq!(hint.kind, X86FixItKind::Removal);
assert!(hint.text.is_empty());
assert_eq!(hint.change_size(), 1);
}
#[test]
fn test_fixit_hint_new_composition() {
let sub1 = X86FixItHint::new_insertion(
4,
"test.h",
1,
1,
"#ifndef TEST_H\n",
"guard start",
X86FixItCategory::Other,
);
let sub2 = X86FixItHint::new_insertion(
5,
"test.h",
10,
1,
"#endif\n",
"guard end",
X86FixItCategory::Other,
);
let comp = X86FixItHint::new_composition(
6,
"test.h",
vec![sub1, sub2],
"add include guard",
X86FixItCategory::Other,
);
assert_eq!(comp.kind, X86FixItKind::Composition);
assert_eq!(comp.sub_hints.len(), 2);
}
#[test]
fn test_fixit_hint_with_diag() {
let hint = X86FixItHint::new_replacement(
7,
"test.c",
1,
1,
1,
1,
"",
"",
X86FixItCategory::MissingSemicolon,
)
.with_diag(DiagID::ErrExpectedSemiAfterExpr);
assert_eq!(hint.diag_ids.len(), 1);
assert_eq!(hint.diag_ids[0], DiagID::ErrExpectedSemiAfterExpr);
}
#[test]
fn test_fixit_hint_with_confidence() {
let hint = X86FixItHint::new_replacement(
8,
"test.c",
1,
1,
1,
1,
"",
"",
X86FixItCategory::TypoCorrection,
)
.with_confidence(0.75);
assert_eq!(hint.confidence, 0.75);
assert!(!hint.is_high_confidence());
}
#[test]
fn test_fixit_hint_disabled() {
let hint =
X86FixItHint::new_replacement(9, "test.c", 1, 1, 1, 1, "", "", X86FixItCategory::Other)
.disabled();
assert!(!hint.enabled);
}
#[test]
fn test_fixit_hint_flatten() {
let sub1 =
X86FixItHint::new_insertion(10, "test.c", 1, 1, "a", "", X86FixItCategory::Other);
let sub2 =
X86FixItHint::new_insertion(11, "test.c", 2, 1, "b", "", X86FixItCategory::Other);
let comp = X86FixItHint::new_composition(
12,
"test.c",
vec![sub1, sub2],
"",
X86FixItCategory::Other,
);
let flat = comp.flatten();
assert_eq!(flat.len(), 2);
}
#[test]
fn test_fixit_hint_change_size_replacement() {
let hint = X86FixItHint::new_replacement(
13,
"test.c",
1,
1,
1,
4,
"x",
"",
X86FixItCategory::Other,
);
assert_eq!(hint.change_size(), 2); }
#[test]
fn test_fixit_hint_change_size_insertion() {
let hint =
X86FixItHint::new_insertion(14, "test.c", 1, 1, "hello", "", X86FixItCategory::Other);
assert_eq!(hint.change_size(), 5);
}
#[test]
fn test_fixit_hint_display() {
let hint = X86FixItHint::new_replacement(
15,
"test.c",
3,
5,
3,
8,
"foo",
"fix typo",
X86FixItCategory::TypoCorrection,
);
let disp = format!("{}", hint);
assert!(disp.contains("FixIt#15"));
assert!(disp.contains("test.c"));
assert!(disp.contains("typo_correction"));
}
#[test]
fn test_fixit_category_display() {
assert_eq!(
format!("{}", X86FixItCategory::MissingSemicolon),
"missing_semicolon"
);
assert_eq!(
format!("{}", X86FixItCategory::OverrideKeyword),
"override_keyword"
);
assert_eq!(
format!("{}", X86FixItCategory::UnsafeFunction),
"unsafe_function"
);
}
#[test]
fn test_fixit_category_is_syntax() {
assert!(X86FixItCategory::MissingSemicolon.is_syntax());
assert!(X86FixItCategory::MissingBrace.is_syntax());
assert!(!X86FixItCategory::TypoCorrection.is_syntax());
assert!(!X86FixItCategory::NullptrConversion.is_syntax());
}
#[test]
fn test_fixit_category_is_typo() {
assert!(X86FixItCategory::TypoCorrection.is_typo());
assert!(X86FixItCategory::TypoInKeyword.is_typo());
assert!(!X86FixItCategory::MissingSemicolon.is_typo());
}
#[test]
fn test_fixit_category_is_modernization() {
assert!(X86FixItCategory::OverrideKeyword.is_modernization());
assert!(X86FixItCategory::NullptrConversion.is_modernization());
assert!(!X86FixItCategory::MissingSemicolon.is_modernization());
}
#[test]
fn test_fixit_category_is_security() {
assert!(X86FixItCategory::UnsafeFunction.is_security());
assert!(X86FixItCategory::BufferOverflow.is_security());
assert!(!X86FixItCategory::PassByValue.is_security());
}
#[test]
fn test_fixit_category_is_performance() {
assert!(X86FixItCategory::PassByValue.is_performance());
assert!(X86FixItCategory::PostIncrement.is_performance());
assert!(!X86FixItCategory::MissingSemicolon.is_performance());
}
#[test]
fn test_fixit_category_priority() {
assert!(
X86FixItCategory::MissingSemicolon.priority()
< X86FixItCategory::TypoCorrection.priority()
);
assert!(
X86FixItCategory::UnsafeFunction.priority() < X86FixItCategory::BraceStyle.priority()
);
}
#[test]
fn test_engine_new() {
let engine = X86FixItEngine::new();
assert!(engine.hints().is_empty());
assert!(engine.stats.total_generated == 0);
assert!(engine.source_files().is_empty());
}
#[test]
fn test_engine_add_source() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "int main() { return 0; }");
assert_eq!(engine.source_files().len(), 1);
assert!(engine.get_source("test.c").is_some());
assert_eq!(
engine.get_source("test.c").unwrap(),
"int main() { return 0; }"
);
}
#[test]
fn test_engine_get_line() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "line1\nline2\nline3");
assert_eq!(engine.get_line("test.c", 2).unwrap(), "line2");
assert!(engine.get_line("test.c", 10).is_none());
}
#[test]
fn test_engine_suggest_missing_semicolon() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "int x = 5");
let hint = engine.suggest_missing_semicolon("test.c", 1, 10);
assert!(hint.is_some());
let h = hint.unwrap();
assert_eq!(h.kind, X86FixItKind::Insertion);
assert_eq!(h.text, ";");
assert_eq!(h.category, X86FixItCategory::MissingSemicolon);
}
#[test]
fn test_engine_suggest_missing_rparen() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "foo(1, 2");
let hint = engine.suggest_missing_rparen("test.c", 1, 8);
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, ")");
}
#[test]
fn test_engine_suggest_missing_rbrace() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "int main() {");
let hint = engine.suggest_missing_rbrace("test.c", 2, 1);
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, "}");
}
#[test]
fn test_engine_suggest_typo_correction() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "prnitf(\"hello\");");
let hint = engine.suggest_typo_correction("test.c", 1, 1, 1, 7, "printf");
assert!(hint.is_some());
let h = hint.unwrap();
assert_eq!(h.text, "printf");
assert_eq!(h.category, X86FixItCategory::TypoCorrection);
}
#[test]
fn test_engine_suggest_operator_correction() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "if (a and b) {}");
let hint = engine.suggest_operator_correction("test.c", 1, 6, 1, 9, "&&");
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, "&&");
}
#[test]
fn test_engine_suggest_missing_include() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "int main() { printf(\"hi\"); }");
let hint = engine.suggest_missing_include("test.c", "stdio.h", 1);
assert!(hint.is_some());
let h = hint.unwrap();
assert!(h.text.contains("stdio.h"));
}
#[test]
fn test_engine_suggest_const_qualification() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "int* p = &x;");
let hint = engine.suggest_const_qualification("test.c", 1, 1);
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, "const ");
}
#[test]
fn test_engine_suggest_override() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "void foo() { }");
let hint = engine.suggest_override("test.cpp", 1, 12);
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, " override");
}
#[test]
fn test_engine_suggest_nullptr() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "int* p = NULL;");
let hint = engine.suggest_nullptr("test.cpp", 1, 10, 1, 14);
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, "nullptr");
}
#[test]
fn test_engine_suggest_static_cast() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "int x = (int)3.14;");
let hint = engine.suggest_static_cast("test.cpp", 1, 9, 1, 17, "int", "3.14");
assert!(hint.is_some());
assert!(hint.unwrap().text.contains("static_cast"));
}
#[test]
fn test_engine_suggest_format_string_fix() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "printf(\"%d\", (size_t)42);");
let hint = engine.suggest_format_string_fix("test.c", 1, 9, 1, 11, "%zu");
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, "%zu");
}
#[test]
fn test_engine_suggest_integer_suffix() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "long x = 2147483648;");
let hint = engine.suggest_integer_suffix("test.c", 1, 22, "L");
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, "L");
}
#[test]
fn test_engine_suggest_float_suffix() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "float x = 3.14;");
let hint = engine.suggest_float_suffix("test.c", 1, 15, "f");
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, "f");
}
#[test]
fn test_engine_suggest_missing_return() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "int foo() { }");
let hint = engine.suggest_missing_return("test.c", 1, 13, "0");
assert!(hint.is_some());
assert!(hint.unwrap().text.contains("return 0"));
}
#[test]
fn test_engine_suggest_include_guard() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.h", "struct Foo { int x; };");
let hint = engine.suggest_include_guard("test.h", "TEST_H", 1, 3);
assert!(hint.is_some());
let h = hint.unwrap();
assert_eq!(h.kind, X86FixItKind::Composition);
assert_eq!(h.sub_hints.len(), 2);
}
#[test]
fn test_engine_category_management() {
let mut engine = X86FixItEngine::new();
assert!(engine.is_category_enabled(X86FixItCategory::MissingSemicolon));
engine.disable_category(X86FixItCategory::MissingSemicolon);
assert!(!engine.is_category_enabled(X86FixItCategory::MissingSemicolon));
engine.enable_category(X86FixItCategory::MissingSemicolon);
assert!(engine.is_category_enabled(X86FixItCategory::MissingSemicolon));
}
#[test]
fn test_engine_min_confidence() {
let mut engine = X86FixItEngine::new();
engine.set_min_confidence(0.9);
let hint = X86FixItHint::new_replacement(
100,
"test.c",
1,
1,
1,
1,
"",
"",
X86FixItCategory::Other,
)
.with_confidence(0.3);
let before = engine.hints().len();
engine.add_hint(hint);
assert_eq!(engine.hints().len(), before); }
#[test]
fn test_engine_max_hints() {
let mut engine = X86FixItEngine::new();
engine.set_max_hints(2);
let h1 = X86FixItHint::new_replacement(
200,
"test.c",
1,
1,
1,
1,
"",
"",
X86FixItCategory::Other,
);
let h2 = X86FixItHint::new_replacement(
201,
"test.c",
1,
1,
1,
1,
"",
"",
X86FixItCategory::Other,
);
let h3 = X86FixItHint::new_replacement(
202,
"test.c",
1,
1,
1,
1,
"",
"",
X86FixItCategory::Other,
);
engine.add_hint(h1);
engine.add_hint(h2);
engine.add_hint(h3);
assert_eq!(engine.hints().len(), 2);
}
#[test]
fn test_engine_apply_replacement() {
let mut engine = X86FixItEngine::new();
let hint = X86FixItHint::new_replacement(
300,
"test.c",
1,
1,
1,
4,
"int",
"",
X86FixItCategory::Other,
);
let result = engine.apply_replacement(&hint, "void foo() {}");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "int foo() {}");
}
#[test]
fn test_engine_apply_insertion() {
let mut engine = X86FixItEngine::new();
let hint = X86FixItHint::new_insertion(
301,
"test.c",
1,
1,
"// comment\n",
"",
X86FixItCategory::Other,
);
let result = engine.apply_insertion(&hint, "int foo() {}");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "// comment\nint foo() {}");
}
#[test]
fn test_engine_apply_removal() {
let mut engine = X86FixItEngine::new();
let hint =
X86FixItHint::new_removal(302, "test.c", 1, 1, 1, 4, "", X86FixItCategory::Other);
let result = engine.apply_removal(&hint, "int foo() {}");
assert!(result.is_ok());
assert_eq!(result.unwrap(), " foo() {}");
}
#[test]
fn test_engine_apply_composition() {
let mut engine = X86FixItEngine::new();
let sub1 = X86FixItHint::new_insertion(
303,
"test.c",
1,
1,
"// top\n",
"",
X86FixItCategory::Other,
);
let sub2 = X86FixItHint::new_insertion(
304,
"test.c",
2,
1,
"// bottom\n",
"",
X86FixItCategory::Other,
);
let comp = X86FixItHint::new_composition(
305,
"test.c",
vec![sub1, sub2],
"",
X86FixItCategory::Other,
);
let result = engine.apply_composition(&comp, "line1\nline2");
assert!(result.is_ok());
let text = result.unwrap();
assert!(text.contains("// top"));
assert!(text.contains("// bottom"));
}
#[test]
fn test_engine_apply_all() {
let mut engine = X86FixItEngine::new();
let hint1 = X86FixItHint::new_replacement(
400,
"test.c",
1,
1,
1,
4,
"int",
"",
X86FixItCategory::Other,
);
let hint2 = X86FixItHint::new_insertion(
401,
"test.c",
1,
10,
";",
"",
X86FixItCategory::MissingSemicolon,
);
engine.add_hint(hint1);
engine.add_hint(hint2);
let result = engine.apply_all("void foo() { return 0 }");
assert!(result.contains("int"));
assert!(result.contains(";"));
}
#[test]
fn test_engine_hints_by_category() {
let mut engine = X86FixItEngine::new();
engine.suggest_missing_semicolon("test.c", 1, 10);
engine.suggest_missing_semicolon("test.c", 2, 10);
engine.suggest_override("test.c", 3, 5);
let semi_hints = engine.hints_by_category(X86FixItCategory::MissingSemicolon);
assert_eq!(semi_hints.len(), 2);
let override_hints = engine.hints_by_category(X86FixItCategory::OverrideKeyword);
assert_eq!(override_hints.len(), 1);
}
#[test]
fn test_engine_hints_by_file() {
let mut engine = X86FixItEngine::new();
engine.suggest_missing_semicolon("a.c", 1, 10);
engine.suggest_missing_semicolon("b.c", 1, 10);
assert_eq!(engine.hints_by_file("a.c").len(), 1);
assert_eq!(engine.hints_by_file("b.c").len(), 1);
assert_eq!(engine.hints_by_file("c.c").len(), 0);
}
#[test]
fn test_engine_high_confidence_hints() {
let mut engine = X86FixItEngine::new();
let h1 = X86FixItHint::new_replacement(
500,
"test.c",
1,
1,
1,
1,
"",
"",
X86FixItCategory::Other,
)
.with_confidence(0.95);
let h2 = X86FixItHint::new_replacement(
501,
"test.c",
1,
1,
1,
1,
"",
"",
X86FixItCategory::Other,
)
.with_confidence(0.5);
engine.add_hint(h1);
engine.add_hint(h2);
let high = engine.high_confidence_hints();
assert_eq!(high.len(), 1);
}
#[test]
fn test_engine_clear_hints() {
let mut engine = X86FixItEngine::new();
engine.suggest_missing_semicolon("test.c", 1, 10);
assert!(!engine.hints().is_empty());
engine.clear_hints();
assert!(engine.hints().is_empty());
}
#[test]
fn test_engine_reset() {
let mut engine = X86FixItEngine::new();
engine.disable_category(X86FixItCategory::MissingSemicolon);
engine.suggest_missing_semicolon("test.c", 1, 10);
engine.reset();
assert!(engine.is_category_enabled(X86FixItCategory::MissingSemicolon));
assert!(engine.hints().is_empty());
}
#[test]
fn test_typo_compute_levenshtein_identical() {
assert_eq!(X86TypoCorrection::compute_levenshtein("hello", "hello"), 0);
}
#[test]
fn test_typo_compute_levenshtein_single_char() {
assert_eq!(X86TypoCorrection::compute_levenshtein("cat", "car"), 1);
}
#[test]
fn test_typo_compute_levenshtein_insertion() {
assert_eq!(X86TypoCorrection::compute_levenshtein("cat", "cats"), 1);
}
#[test]
fn test_typo_compute_levenshtein_deletion() {
assert_eq!(X86TypoCorrection::compute_levenshtein("cats", "cat"), 1);
}
#[test]
fn test_typo_compute_levenshtein_known_distance() {
assert_eq!(
X86TypoCorrection::compute_levenshtein("kitten", "sitting"),
3
);
}
#[test]
fn test_typo_compute_levenshtein_empty() {
assert_eq!(X86TypoCorrection::compute_levenshtein("", "abc"), 3);
assert_eq!(X86TypoCorrection::compute_levenshtein("abc", ""), 3);
assert_eq!(X86TypoCorrection::compute_levenshtein("", ""), 0);
}
#[test]
fn test_typo_compute_levenshtein_transposition() {
assert_eq!(X86TypoCorrection::compute_levenshtein("ab", "ba"), 1);
}
#[test]
fn test_typo_edit_distance_cached() {
let mut typo = X86TypoCorrection::new();
let d1 = typo.edit_distance("printf", "prnitf");
let d2 = typo.edit_distance("printf", "prnitf");
assert_eq!(d1, d2);
assert!(typo.stats.cache_hits > 0);
}
#[test]
fn test_typo_similarity() {
let mut typo = X86TypoCorrection::new();
let sim = typo.similarity("hello", "hello");
assert!((sim - 1.0).abs() < 0.001);
let sim2 = typo.similarity("hello", "hallo");
assert!(sim2 > 0.5);
}
#[test]
fn test_typo_find_best_match() {
let mut typo = X86TypoCorrection::new();
let candidates = vec![
"printf".to_string(),
"sprintf".to_string(),
"fprintf".to_string(),
];
let result = typo.find_best_match("prnitf", &candidates);
assert!(result.is_some());
let (correction, dist, _) = result.unwrap();
assert_eq!(correction, "printf");
assert!(dist <= 2);
}
#[test]
fn test_typo_search_keywords() {
let mut typo = X86TypoCorrection::new();
let result = typo.search_keywords("retrun");
assert!(result.is_some());
let (correction, dist, _) = result.unwrap();
assert_eq!(correction, "return");
assert!(dist <= 2);
}
#[test]
fn test_typo_search_keywords_no_match() {
let mut typo = X86TypoCorrection::new();
let result = typo.search_keywords("xyznonexistent123");
assert!(result.is_none());
}
#[test]
fn test_typo_search_type_names() {
let mut typo = X86TypoCorrection::new();
let result = typo.search_type_names("size_t");
assert!(result.is_some());
}
#[test]
fn test_typo_search_in_scope() {
let mut typo = X86TypoCorrection::new();
typo.register_scope(
"test.c",
vec!["my_variable".to_string(), "my_function".to_string()],
);
let result = typo.search_in_scope("my_varible", "test.c");
assert!(result.is_some());
let (correction, _, _) = result.unwrap();
assert_eq!(correction, "my_variable");
}
#[test]
fn test_typo_search_operator() {
let mut typo = X86TypoCorrection::new();
let result = typo.search_operator("adn");
assert!(result.is_some());
let (alt, std_op, _) = result.unwrap();
assert_eq!(std_op, "&&");
}
#[test]
fn test_typo_correct_typo_keyword() {
let mut typo = X86TypoCorrection::new();
let result = typo.correct_typo("whiel", "test.c");
assert!(result.is_some());
let r = result.unwrap();
assert_eq!(r.correction, "while");
assert_eq!(r.kind, X86TypoCorrectionKind::Keyword);
}
#[test]
fn test_typo_correct_typo_exact_match() {
let mut typo = X86TypoCorrection::new();
let result = typo.correct_typo("while", "test.c");
assert!(result.is_none()); }
#[test]
fn test_typo_correct_unqualified_lookup() {
let mut typo = X86TypoCorrection::new();
typo.add_global_identifier("calculate_sum");
let result = typo.correct_unqualified_lookup("calulate_sum", "test.c");
assert!(result.is_some());
let r = result.unwrap();
assert_eq!(r.correction, "calculate_sum");
}
#[test]
fn test_typo_correct_member_access() {
let mut typo = X86TypoCorrection::new();
let members = vec![
"firstName".to_string(),
"lastName".to_string(),
"middleName".to_string(),
];
let result = typo.correct_member_access("frstName", &members);
assert!(result.is_some());
let r = result.unwrap();
assert_eq!(r.correction, "firstName");
}
#[test]
fn test_typo_correct_template_name() {
let mut typo = X86TypoCorrection::new();
let templates = vec!["std::vector".to_string(), "std::list".to_string()];
let result = typo.correct_template_name("std::vectr", &templates);
assert!(result.is_some());
let r = result.unwrap();
assert_eq!(r.correction, "std::vector");
}
#[test]
fn test_typo_register_scope() {
let mut typo = X86TypoCorrection::new();
typo.register_scope("main.c", vec!["x".to_string(), "y".to_string()]);
let ids = typo.get_scope_identifiers("main.c");
assert!(ids.contains(&"x".to_string()));
assert!(ids.contains(&"y".to_string()));
}
#[test]
fn test_typo_result_is_high_confidence() {
let result = X86TypoCorrectionResult {
typo: "abc".into(),
correction: "abd".into(),
kind: X86TypoCorrectionKind::Identifier,
distance: 1,
confidence: 0.9,
};
assert!(result.is_high_confidence());
let result2 = X86TypoCorrectionResult {
typo: "abc".into(),
correction: "xyz".into(),
kind: X86TypoCorrectionKind::Identifier,
distance: 3,
confidence: 0.5,
};
assert!(!result2.is_high_confidence());
}
#[test]
fn test_typo_kind_display() {
assert_eq!(format!("{}", X86TypoCorrectionKind::Keyword), "keyword");
assert_eq!(
format!("{}", X86TypoCorrectionKind::Identifier),
"identifier"
);
assert_eq!(
format!("{}", X86TypoCorrectionKind::MemberAccess),
"member_access"
);
}
#[test]
fn test_missing_include_get_header_for_symbol() {
let mi = X86MissingInclude::new();
assert_eq!(
mi.get_header_for_symbol("printf"),
Some(&"stdio.h".to_string())
);
assert_eq!(
mi.get_header_for_symbol("malloc"),
Some(&"stdlib.h".to_string())
);
assert_eq!(
mi.get_header_for_symbol("memcpy"),
Some(&"string.h".to_string())
);
}
#[test]
fn test_missing_include_is_standard_symbol() {
let mi = X86MissingInclude::new();
assert!(mi.is_standard_symbol("printf"));
assert!(mi.is_standard_symbol("std::string"));
assert!(!mi.is_standard_symbol("my_custom_func"));
}
#[test]
fn test_missing_include_c_headers() {
let mi = X86MissingInclude::new();
let headers = mi.c_headers();
assert!(headers.contains(&"stdio.h".to_string()));
assert!(headers.contains(&"stdlib.h".to_string()));
assert!(headers.contains(&"string.h".to_string()));
}
#[test]
fn test_missing_include_cpp_headers() {
let mi = X86MissingInclude::new();
let headers = mi.cpp_headers();
assert!(headers.contains(&"vector".to_string()));
assert!(headers.contains(&"string".to_string()));
assert!(headers.contains(&"memory".to_string()));
}
#[test]
fn test_missing_include_system_headers() {
let mi = X86MissingInclude::new();
let headers = mi.system_headers();
assert!(headers.contains(&"unistd.h".to_string()));
assert!(headers.contains(&"immintrin.h".to_string()));
}
#[test]
fn test_missing_include_find_insertion_location() {
let mut mi = X86MissingInclude::new();
let source = "#include <stdio.h>\n#include <stdlib.h>\n\nint main() {}\n";
let loc = mi.find_insertion_location("test.c", source);
assert_eq!(loc, 3); }
#[test]
fn test_missing_include_find_insertion_location_no_includes() {
let mut mi = X86MissingInclude::new();
let source = "int main() { return 0; }\n";
let loc = mi.find_insertion_location("test2.c", source);
assert_eq!(loc, 1); }
#[test]
fn test_missing_include_find_insertion_location_cached() {
let mut mi = X86MissingInclude::new();
let source = "#include <stdio.h>\nint main() {}\n";
let loc1 = mi.find_insertion_location("test.c", source);
let loc2 = mi.find_insertion_location("test.c", source);
assert_eq!(loc1, loc2); }
#[test]
fn test_missing_include_infer_header_from_symbol_c() {
let mi = X86MissingInclude::new();
let headers = mi.infer_header_from_symbol("strlen");
assert!(headers.contains(&"string.h".to_string()));
let headers2 = mi.infer_header_from_symbol("malloc");
assert!(headers2.contains(&"stdlib.h".to_string()));
}
#[test]
fn test_missing_include_infer_header_from_symbol_cpp() {
let mi = X86MissingInclude::new();
let headers = mi.infer_header_from_symbol("std::string");
assert!(headers.contains(&"string".to_string()));
}
#[test]
fn test_missing_include_clear_locations() {
let mut mi = X86MissingInclude::new();
mi.find_insertion_location("test.c", "int x;");
assert!(mi.stats.locations_detected > 0);
mi.clear_locations();
let loc = mi.find_insertion_location("test.c", "#include <a.h>\nint x;");
assert_eq!(loc, 2);
}
#[test]
fn test_modernization_new() {
let cm = X86CodeModernization::new();
assert!(cm.is_enabled(ModernizationCheck::Nullptr));
assert!(cm.is_enabled(ModernizationCheck::Override));
assert!(cm.is_enabled(ModernizationCheck::RangeForLoop));
}
#[test]
fn test_modernization_enable_disable() {
let mut cm = X86CodeModernization::new();
cm.disable(ModernizationCheck::Nullptr);
assert!(!cm.is_enabled(ModernizationCheck::Nullptr));
cm.enable(ModernizationCheck::Nullptr);
assert!(cm.is_enabled(ModernizationCheck::Nullptr));
}
#[test]
fn test_modernization_nullptr_suggestion() {
let cm = X86CodeModernization::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "int* p = NULL;");
let hint = cm.suggest_nullptr(&mut engine, "test.cpp", 1, 10, 1, 14);
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, "nullptr");
}
#[test]
fn test_modernization_nullptr_disabled() {
let mut cm = X86CodeModernization::new();
cm.disable(ModernizationCheck::Nullptr);
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "int* p = NULL;");
let hint = cm.suggest_nullptr(&mut engine, "test.cpp", 1, 10, 1, 14);
assert!(hint.is_none());
}
#[test]
fn test_modernization_make_unique() {
let cm = X86CodeModernization::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "auto p = std::unique_ptr<Foo>(new Foo());");
let hint = cm.suggest_make_unique(&mut engine, "test.cpp", 1, 10, 1, 48, "Foo", "");
assert!(hint.is_some());
assert!(hint.unwrap().text.contains("make_unique"));
}
#[test]
fn test_modernization_make_shared() {
let cm = X86CodeModernization::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "auto p = std::shared_ptr<Foo>(new Foo(42));");
let hint = cm.suggest_make_shared(&mut engine, "test.cpp", 1, 10, 1, 52, "Foo", "42");
assert!(hint.is_some());
assert!(hint.unwrap().text.contains("make_shared"));
}
#[test]
fn test_modernization_constexpr() {
let cm = X86CodeModernization::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "int square(int x) { return x * x; }");
let hint = cm.suggest_constexpr(&mut engine, "test.cpp", 1, 1);
assert!(hint.is_some());
assert!(hint.unwrap().text.contains("constexpr"));
}
#[test]
fn test_modernization_noexcept() {
let cm = X86CodeModernization::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "void foo() { }");
let hint = cm.suggest_noexcept(&mut engine, "test.cpp", 1, 12);
assert!(hint.is_some());
assert!(hint.unwrap().text.contains("noexcept"));
}
#[test]
fn test_modernization_emplace_back() {
let cm = X86CodeModernization::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "vec.push_back(Foo(1, 2));");
let hint = cm.suggest_emplace_back(&mut engine, "test.cpp", 1, 5, 1, 14);
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, "emplace_back");
}
#[test]
fn test_modernization_range_for() {
let cm = X86CodeModernization::new();
let mut engine = X86FixItEngine::new();
engine.add_source(
"test.cpp",
"for (auto it = v.begin(); it != v.end(); ++it) {}",
);
let hint = cm.suggest_range_for(&mut engine, "test.cpp", 1, 1, 1, 52, "v", "x");
assert!(hint.is_some());
assert!(hint.unwrap().text.contains("auto&&"));
}
#[test]
fn test_modernization_using_alias() {
let cm = X86CodeModernization::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "typedef std::vector<int> VecInt;");
let hint = cm.suggest_using_alias(
&mut engine,
"test.cpp",
1,
1,
1,
30,
"VecInt",
"std::vector<int>",
);
assert!(hint.is_some());
assert!(hint.unwrap().text.contains("using"));
}
#[test]
fn test_modernization_check_display() {
assert_eq!(
format!("{}", ModernizationCheck::Nullptr),
"modernize-use-nullptr"
);
assert_eq!(
format!("{}", ModernizationCheck::Override),
"modernize-use-override"
);
assert_eq!(
format!("{}", ModernizationCheck::Constexpr),
"modernize-use-constexpr"
);
}
#[test]
fn test_security_is_unsafe() {
let sf = X86SecurityFixIt::new();
assert!(sf.is_unsafe("gets"));
assert!(sf.is_unsafe("strcpy"));
assert!(sf.is_unsafe("sprintf"));
assert!(sf.is_unsafe("scanf"));
assert!(!sf.is_unsafe("printf"));
assert!(!sf.is_unsafe("my_func"));
}
#[test]
fn test_security_get_safe_replacement() {
let sf = X86SecurityFixIt::new();
let repl = sf.get_safe_replacement("gets");
assert!(repl.is_some());
let r = repl.unwrap();
assert_eq!(r.function, "fgets");
assert_eq!(r.severity, 10);
}
#[test]
fn test_security_suggest_safe_replacement() {
let sf = X86SecurityFixIt::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "gets(buffer);");
let hint = sf.suggest_safe_replacement(&mut engine, "test.c", 1, 1, 1, 5, "gets");
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, "fgets");
}
#[test]
fn test_security_suggest_safe_replacement_unknown() {
let sf = X86SecurityFixIt::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "my_func();");
let hint = sf.suggest_safe_replacement(&mut engine, "test.c", 1, 1, 1, 8, "my_func");
assert!(hint.is_none());
}
#[test]
fn test_security_unsafe_functions() {
let sf = X86SecurityFixIt::new();
let funcs = sf.unsafe_functions();
assert!(funcs.iter().any(|f| f.as_str() == "gets"));
assert!(funcs.iter().any(|f| f.as_str() == "strcpy"));
}
#[test]
fn test_security_generate_all_hints() {
let mut sf = X86SecurityFixIt::new();
let mut engine = X86FixItEngine::new();
engine.add_source(
"test.c",
"gets(buf);\nstrcpy(dst, src);\nsprintf(buf, \"%d\", x);",
);
let mut hints = vec![];
let sources: HashMap<String, String> = [(
"test.c".to_string(),
"gets(buf);\nstrcpy(dst, src);\nsprintf(buf, \"%d\", x);".to_string(),
)]
.into_iter()
.collect();
sf.generate_all_hints(&mut hints, &sources);
assert!(hints.len() >= 1);
}
#[test]
fn test_security_strcpy_severity() {
let sf = X86SecurityFixIt::new();
let repl = sf.get_safe_replacement("strcpy").unwrap();
assert_eq!(repl.function, "strncpy");
assert_eq!(repl.severity, 8);
}
#[test]
fn test_security_sprintf_severity() {
let sf = X86SecurityFixIt::new();
let repl = sf.get_safe_replacement("sprintf").unwrap();
assert_eq!(repl.function, "snprintf");
}
#[test]
fn test_performance_new() {
let pf = X86PerformanceFixIt::new();
assert!(pf.is_enabled(PerformanceCheck::PassByValue));
assert!(pf.is_enabled(PerformanceCheck::PostIncrement));
assert!(pf.is_enabled(PerformanceCheck::ReserveBeforePushBack));
}
#[test]
fn test_performance_enable_disable() {
let mut pf = X86PerformanceFixIt::new();
pf.disable(PerformanceCheck::PostIncrement);
assert!(!pf.is_enabled(PerformanceCheck::PostIncrement));
pf.enable(PerformanceCheck::PostIncrement);
assert!(pf.is_enabled(PerformanceCheck::PostIncrement));
}
#[test]
fn test_performance_suggest_const_reference() {
let pf = X86PerformanceFixIt::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "void foo(std::string s) { }");
let hint =
pf.suggest_const_reference(&mut engine, "test.cpp", 1, 10, 1, 23, "std::string", "s");
assert!(hint.is_some());
assert!(hint.unwrap().text.contains("const"));
assert!(hint.unwrap().text.contains("&"));
}
#[test]
fn test_performance_suggest_pre_increment() {
let pf = X86PerformanceFixIt::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "for (int i = 0; i < n; i++) {}");
let hint = pf.suggest_pre_increment(&mut engine, "test.cpp", 1, 22, 1, 25);
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, "++");
}
#[test]
fn test_performance_suggest_reserve_before_push_back() {
let pf = X86PerformanceFixIt::new();
let mut engine = X86FixItEngine::new();
engine.add_source(
"test.cpp",
"std::vector<int> v;\nfor (...) { v.push_back(x); }",
);
let hint = pf.suggest_reserve_before_push_back(&mut engine, "test.cpp", 2, 1, "v", "100");
assert!(hint.is_some());
assert!(hint.unwrap().text.contains("reserve"));
}
#[test]
fn test_performance_suggest_emplace_back() {
let pf = X86PerformanceFixIt::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "vec.push_back(Foo(1));");
let hint = pf.suggest_emplace_back(&mut engine, "test.cpp", 1, 5, 1, 14);
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, "emplace_back");
}
#[test]
fn test_performance_suggest_move() {
let pf = X86PerformanceFixIt::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "return local_str;");
let hint = pf.suggest_move(&mut engine, "test.cpp", 1, 8, "local_str");
assert!(hint.is_some());
assert!(hint.unwrap().text.contains("std::move"));
}
#[test]
fn test_performance_check_display() {
assert_eq!(
format!("{}", PerformanceCheck::PassByValue),
"performance-pass-by-value"
);
assert_eq!(
format!("{}", PerformanceCheck::PostIncrement),
"performance-post-increment"
);
}
#[test]
fn test_performance_pass_by_value_disabled() {
let mut pf = X86PerformanceFixIt::new();
pf.disable(PerformanceCheck::PassByValue);
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "void foo(std::string s) { }");
let hint =
pf.suggest_const_reference(&mut engine, "test.cpp", 1, 10, 1, 23, "std::string", "s");
assert!(hint.is_none());
}
#[test]
fn test_style_new_defaults() {
let sf = X86StyleFixIt::new();
assert_eq!(sf.brace_style(), BraceStyle::Attached);
}
#[test]
fn test_style_set_brace_style() {
let mut sf = X86StyleFixIt::new();
sf.set_brace_style(BraceStyle::NewLine);
assert_eq!(sf.brace_style(), BraceStyle::NewLine);
}
#[test]
fn test_style_set_naming_convention() {
let mut sf = X86StyleFixIt::new();
sf.set_naming_convention(NamingConvention::SnakeCase);
let name = sf.convert_name("MyVariable");
assert_eq!(name, "my_variable");
}
#[test]
fn test_style_convert_to_snake_case() {
let sf = X86StyleFixIt::new();
let name = X86StyleFixIt::to_snake_case("MyVariableName");
assert_eq!(name, "my_variable_name");
}
#[test]
fn test_style_convert_to_pascal_case() {
let name = X86StyleFixIt::to_pascal_case("my_variable_name");
assert_eq!(name, "MyVariableName");
}
#[test]
fn test_style_convert_to_camel_case() {
let name = X86StyleFixIt::to_camel_case("my_variable_name");
assert_eq!(name, "myVariableName");
}
#[test]
fn test_style_convert_upper_case() {
let mut sf = X86StyleFixIt::new();
sf.set_naming_convention(NamingConvention::UpperCase);
let name = sf.convert_name("myVar");
assert_eq!(name, "MYVAR");
}
#[test]
fn test_style_convert_m_prefix() {
let mut sf = X86StyleFixIt::new();
sf.set_naming_convention(NamingConvention::MPrefix);
let name = sf.convert_name("my_variable");
assert_eq!(name, "m_myVariable");
}
#[test]
fn test_style_suggest_attached_brace() {
let sf = X86StyleFixIt::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "void foo()\n{\n}");
let hint = sf.suggest_attached_brace(&mut engine, "test.cpp", 2, 1, 0);
assert!(hint.is_some());
}
#[test]
fn test_style_suggest_rename() {
let mut sf = X86StyleFixIt::new();
sf.set_naming_convention(NamingConvention::SnakeCase);
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "int MyVariable = 42;");
let hint = sf.suggest_rename(&mut engine, "test.cpp", 1, 5, 1, 16, "my_variable");
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, "my_variable");
}
#[test]
fn test_style_suggest_indent_correction() {
let sf = X86StyleFixIt::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "int x = 1;");
let hint = sf.suggest_indent_correction(&mut engine, "test.cpp", 1, 1);
assert!(hint.is_some());
assert!(!hint.unwrap().text.is_empty());
}
#[test]
fn test_style_suggest_remove_trailing_whitespace() {
let sf = X86StyleFixIt::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.cpp", "int x = 1; ");
let hint = sf.suggest_remove_trailing_whitespace(&mut engine, "test.cpp", 1, 12);
assert!(hint.is_some());
assert_eq!(hint.unwrap().kind, X86FixItKind::Removal);
}
#[test]
fn test_style_suggest_line_break() {
let sf = X86StyleFixIt::new();
let mut engine = X86FixItEngine::new();
let long_line = "x".repeat(100);
engine.add_source("test.cpp", &long_line);
let hint = sf.suggest_line_break(&mut engine, "test.cpp", 1, 80);
assert!(hint.is_some());
}
#[test]
fn test_style_brace_style_display() {
assert_eq!(format!("{}", BraceStyle::Attached), "attached");
assert_eq!(format!("{}", BraceStyle::NewLine), "newline");
}
#[test]
fn test_style_naming_convention_display() {
assert_eq!(format!("{}", NamingConvention::SnakeCase), "snake_case");
assert_eq!(format!("{}", NamingConvention::CamelCase), "camelCase");
assert_eq!(format!("{}", NamingConvention::PascalCase), "PascalCase");
}
#[test]
fn test_style_check_display() {
assert_eq!(format!("{}", StyleCheck::BraceStyle), "style-brace-style");
assert_eq!(format!("{}", StyleCheck::NamingConvention), "style-naming");
}
#[test]
fn test_style_indent_width_clamp() {
let mut sf = X86StyleFixIt::new();
sf.set_indent_width(0); assert!(sf.indent_width >= 1);
sf.set_indent_width(20); assert!(sf.indent_width <= 16);
}
#[test]
fn test_portability_data_model_default() {
let model = X86DataModel::default();
assert_eq!(model, X86DataModel::LP64);
}
#[test]
fn test_portability_data_model_sizeof_long() {
assert_eq!(X86DataModel::ILP32.sizeof_long(), 4);
assert_eq!(X86DataModel::LP64.sizeof_long(), 8);
assert_eq!(X86DataModel::LLP64.sizeof_long(), 4);
assert_eq!(X86DataModel::ILP64.sizeof_long(), 8);
}
#[test]
fn test_portability_data_model_sizeof_pointer() {
assert_eq!(X86DataModel::ILP32.sizeof_pointer(), 4);
assert_eq!(X86DataModel::LP64.sizeof_pointer(), 8);
assert_eq!(X86DataModel::LLP64.sizeof_pointer(), 8);
}
#[test]
fn test_portability_data_model_size_t_format() {
assert_eq!(X86DataModel::LP64.size_t_format(), "%zu");
assert_eq!(X86DataModel::ILP32.size_t_format(), "%u");
assert_eq!(X86DataModel::LLP64.size_t_format(), "%Iu");
}
#[test]
fn test_portability_data_model_ptrdiff_t_format() {
assert_eq!(X86DataModel::LP64.ptrdiff_t_format(), "%zd");
assert_eq!(X86DataModel::LLP64.ptrdiff_t_format(), "%Id");
}
#[test]
fn test_portability_data_model_display() {
assert_eq!(format!("{}", X86DataModel::LP64), "LP64");
assert_eq!(format!("{}", X86DataModel::LLP64), "LLP64");
}
#[test]
fn test_portability_suggest_size_t_format() {
let pf = X86PortabilityFixIt::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "printf(\"%lu\", (unsigned long)sizeof(x));");
let hint = pf.suggest_size_t_format(&mut engine, "test.c", 1, 9, 1, 12);
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, "%zu");
}
#[test]
fn test_portability_suggest_fixed_width_type() {
let pf = X86PortabilityFixIt::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "long counter = 0;");
let hint = pf.suggest_fixed_width_type(&mut engine, "test.c", 1, 1, 1, 5, "int64_t");
assert!(hint.is_some());
assert_eq!(hint.unwrap().text, "int64_t");
}
#[test]
fn test_portability_suggest_packed_attribute() {
let pf = X86PortabilityFixIt::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "struct Foo { char c; int x; };");
let hint = pf.suggest_packed_attribute(&mut engine, "test.c", 1, 12);
assert!(hint.is_some());
assert!(hint.unwrap().text.contains("packed"));
}
#[test]
fn test_portability_suggest_pragma_pack() {
let pf = X86PortabilityFixIt::new();
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "struct Foo { char c; int x; };");
let hint = pf.suggest_pragma_pack(&mut engine, "test.c", 1, 1);
assert!(hint.is_some());
assert!(hint.unwrap().text.contains("#pragma pack"));
}
#[test]
fn test_portability_set_data_model() {
let mut pf = X86PortabilityFixIt::new();
pf.set_data_model(X86DataModel::LLP64);
assert_eq!(pf.data_model(), X86DataModel::LLP64);
}
#[test]
fn test_portability_check_display() {
assert_eq!(
format!("{}", PortabilityCheck::SizeTFormat),
"portability-size-t-format"
);
assert_eq!(
format!("{}", PortabilityCheck::StructPacking),
"portability-struct-packing"
);
}
#[test]
fn test_conflict_resolver_no_overlap() {
let h1 = X86FixItHint::new_replacement(
1,
"test.c",
1,
1,
1,
5,
"x",
"",
X86FixItCategory::Other,
);
let h2 = X86FixItHint::new_replacement(
2,
"test.c",
2,
1,
2,
5,
"y",
"",
X86FixItCategory::Other,
);
assert!(!X86FixItConflictResolver::has_overlap(&h1, &h2));
}
#[test]
fn test_conflict_resolver_overlap() {
let h1 = X86FixItHint::new_replacement(
1,
"test.c",
1,
1,
1,
10,
"x",
"",
X86FixItCategory::Other,
);
let h2 = X86FixItHint::new_replacement(
2,
"test.c",
1,
5,
1,
15,
"y",
"",
X86FixItCategory::Other,
);
assert!(X86FixItConflictResolver::has_overlap(&h1, &h2));
}
#[test]
fn test_conflict_resolver_different_files() {
let h1 =
X86FixItHint::new_replacement(1, "a.c", 1, 1, 1, 5, "x", "", X86FixItCategory::Other);
let h2 =
X86FixItHint::new_replacement(2, "b.c", 1, 1, 1, 5, "y", "", X86FixItCategory::Other);
assert!(!X86FixItConflictResolver::has_overlap(&h1, &h2));
}
#[test]
fn test_conflict_resolver_detect_conflicts() {
let mut resolver = X86FixItConflictResolver::new();
let h1 = X86FixItHint::new_replacement(
1,
"test.c",
1,
1,
1,
10,
"a",
"",
X86FixItCategory::Other,
);
let h2 = X86FixItHint::new_replacement(
2,
"test.c",
1,
5,
1,
15,
"b",
"",
X86FixItCategory::Other,
);
let h3 = X86FixItHint::new_replacement(
3,
"test.c",
3,
1,
3,
5,
"c",
"",
X86FixItCategory::Other,
);
let conflicts = resolver.detect_conflicts(&[h1, h2, h3]);
assert_eq!(conflicts.len(), 1); }
#[test]
fn test_conflict_resolver_resolve_keep_highest_confidence() {
let mut resolver = X86FixItConflictResolver::new();
resolver.set_strategy(ConflictResolutionStrategy::KeepHighestConfidence);
let h1 = X86FixItHint::new_replacement(
1,
"test.c",
1,
1,
1,
10,
"a",
"",
X86FixItCategory::Other,
)
.with_confidence(0.5);
let h2 = X86FixItHint::new_replacement(
2,
"test.c",
1,
5,
1,
15,
"b",
"",
X86FixItCategory::Other,
)
.with_confidence(0.9);
resolver.detect_conflicts(&[h1.clone(), h2.clone()]);
let kept = resolver.resolve(&[h1, h2]);
assert_eq!(kept, vec![1]); }
#[test]
fn test_conflict_resolver_resolve_keep_highest_priority() {
let mut resolver = X86FixItConflictResolver::new();
resolver.set_strategy(ConflictResolutionStrategy::KeepHighestPriority);
let h1 = X86FixItHint::new_replacement(
1,
"test.c",
1,
1,
1,
10,
"a",
"",
X86FixItCategory::MissingSemicolon,
);
let h2 = X86FixItHint::new_replacement(
2,
"test.c",
1,
5,
1,
15,
"b",
"",
X86FixItCategory::BraceStyle,
);
resolver.detect_conflicts(&[h1.clone(), h2.clone()]);
let kept = resolver.resolve(&[h1, h2]);
assert_eq!(kept, vec![0]); }
#[test]
fn test_conflict_resolver_strategy_display() {
let strategy = ConflictResolutionStrategy::default();
assert_eq!(strategy, ConflictResolutionStrategy::KeepHighestConfidence);
}
#[test]
fn test_applicator_new() {
let app = X86FixItApplicator::new();
assert!(app.applied.is_empty());
assert!(app.rejected.is_empty());
assert_eq!(app.applied_count(), 0);
assert_eq!(app.rejected_count(), 0);
}
#[test]
fn test_applicator_apply_replacement() {
let mut app = X86FixItApplicator::new();
let hint = X86FixItHint::new_replacement(
1,
"test.c",
1,
1,
1,
4,
"int",
"",
X86FixItCategory::Other,
);
let result = app.apply_one(&hint, "void foo() {}");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "int foo() {}");
assert_eq!(app.applied_count(), 1);
}
#[test]
fn test_applicator_apply_insertion() {
let mut app = X86FixItApplicator::new();
let hint = X86FixItHint::new_insertion(
2,
"test.c",
1,
15,
";",
"",
X86FixItCategory::MissingSemicolon,
);
let result = app.apply_one(&hint, "int x = 5");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "int x = 5;");
}
#[test]
fn test_applicator_apply_removal() {
let mut app = X86FixItApplicator::new();
let hint = X86FixItHint::new_removal(
3,
"test.c",
1,
11,
1,
12,
"",
X86FixItCategory::ExtraSemicolon,
);
let result = app.apply_one(&hint, "int x = 5;;");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "int x = 5;");
}
#[test]
fn test_applicator_apply_composition() {
let mut app = X86FixItApplicator::new();
let sub1 =
X86FixItHint::new_insertion(4, "test.c", 1, 1, "// top\n", "", X86FixItCategory::Other);
let sub2 = X86FixItHint::new_insertion(
5,
"test.c",
2,
1,
"// bottom\n",
"",
X86FixItCategory::Other,
);
let comp = X86FixItHint::new_composition(
6,
"test.c",
vec![sub1, sub2],
"",
X86FixItCategory::Other,
);
let result = app.apply_one(&comp, "line1\nline2");
assert!(result.is_ok());
let text = result.unwrap();
assert!(text.contains("// top"));
assert!(text.contains("// bottom"));
}
#[test]
fn test_applicator_apply_invalid_line() {
let mut app = X86FixItApplicator::new();
let hint = X86FixItHint::new_replacement(
7,
"test.c",
10,
1,
10,
5,
"x",
"",
X86FixItCategory::Other,
);
let result = app.apply_one(&hint, "line1");
assert!(result.is_err());
assert_eq!(app.rejected_count(), 1);
}
#[test]
fn test_applicator_apply_all_with_conflicts() {
let mut app = X86FixItApplicator::new();
let h1 = X86FixItHint::new_replacement(
10,
"test.c",
1,
1,
1,
10,
"first",
"",
X86FixItCategory::Other,
)
.with_confidence(0.9);
let h2 = X86FixItHint::new_replacement(
11,
"test.c",
1,
5,
1,
15,
"second",
"",
X86FixItCategory::Other,
)
.with_confidence(0.5);
let result = app.apply_all(&[h1, h2], "123456789012345");
assert!(result.contains("first"));
}
#[test]
fn test_applicator_clear() {
let mut app = X86FixItApplicator::new();
let hint =
X86FixItHint::new_insertion(12, "test.c", 1, 1, "x", "", X86FixItCategory::Other);
let _ = app.apply_one(&hint, "");
assert!(app.applied_count() > 0);
app.clear();
assert_eq!(app.applied_count(), 0);
assert_eq!(app.rejected_count(), 0);
}
#[test]
fn test_diff_compute_no_changes() {
let diff = X86FixItDiff::compute("line1\nline2", "line1\nline2", "test.c");
assert_eq!(diff.file, "test.c");
assert!(diff
.hunks
.iter()
.all(|h| h.lines.iter().all(|l| matches!(l, X86DiffLine::Context(_)))));
}
#[test]
fn test_diff_compute_with_changes() {
let diff = X86FixItDiff::compute("old line", "new line", "test.c");
assert_eq!(diff.file, "test.c");
assert!(diff
.hunks
.iter()
.any(|h| h.lines.iter().any(|l| matches!(l, X86DiffLine::Removal(_)))));
assert!(diff.hunks.iter().any(|h| h
.lines
.iter()
.any(|l| matches!(l, X86DiffLine::Addition(_)))));
}
#[test]
fn test_diff_render() {
let diff = X86FixItDiff::compute("old\n", "new\n", "test.c");
let rendered = diff.render();
assert!(rendered.contains("--- a/test.c"));
assert!(rendered.contains("+++ b/test.c"));
}
#[test]
fn test_diff_line_display() {
assert_eq!(format!("{}", X86DiffLine::Context("foo".into())), " foo");
assert_eq!(format!("{}", X86DiffLine::Addition("bar".into())), "+ bar");
assert_eq!(format!("{}", X86DiffLine::Removal("baz".into())), "- baz");
}
#[test]
fn test_validator_validate_valid() {
let mut validator = X86FixItValidator::new();
let hint = X86FixItHint::new_replacement(
1,
"test.c",
1,
1,
1,
4,
"int",
"",
X86FixItCategory::Other,
);
let mut sources = HashMap::new();
sources.insert("test.c".to_string(), "void foo() {}".to_string());
assert!(validator.validate(&hint, &sources));
assert_eq!(validator.validated_count(), 1);
}
#[test]
fn test_validator_validate_missing_file() {
let mut validator = X86FixItValidator::new();
let hint = X86FixItHint::new_replacement(
2,
"nonexistent.c",
1,
1,
1,
1,
"",
"",
X86FixItCategory::Other,
);
let sources = HashMap::new();
assert!(!validator.validate(&hint, &sources));
assert_eq!(validator.failed_count(), 1);
}
#[test]
fn test_validator_validate_invalid_line() {
let mut validator = X86FixItValidator::new();
let hint = X86FixItHint::new_replacement(
3,
"test.c",
10,
1,
10,
5,
"",
"",
X86FixItCategory::Other,
);
let mut sources = HashMap::new();
sources.insert("test.c".to_string(), "line1".to_string());
assert!(!validator.validate(&hint, &sources));
}
#[test]
fn test_validator_validate_invalid_confidence() {
let mut validator = X86FixItValidator::new();
let hint =
X86FixItHint::new_replacement(4, "test.c", 1, 1, 1, 1, "", "", X86FixItCategory::Other)
.with_confidence(1.5); let mut sources = HashMap::new();
sources.insert("test.c".to_string(), "line1".to_string());
assert!(!validator.validate(&hint, &sources));
}
#[test]
fn test_validator_validate_composition() {
let mut validator = X86FixItValidator::new();
let sub = X86FixItHint::new_insertion(5, "test.c", 1, 1, "x", "", X86FixItCategory::Other);
let comp =
X86FixItHint::new_composition(6, "test.c", vec![sub], "", X86FixItCategory::Other);
let mut sources = HashMap::new();
sources.insert("test.c".to_string(), "hello".to_string());
assert!(validator.validate(&comp, &sources));
}
#[test]
fn test_validator_validate_composition_empty() {
let mut validator = X86FixItValidator::new();
let comp = X86FixItHint::new_composition(7, "test.c", vec![], "", X86FixItCategory::Other);
let mut sources = HashMap::new();
sources.insert("test.c".to_string(), "hello".to_string());
assert!(!validator.validate(&comp, &sources));
}
#[test]
fn test_validator_validate_all() {
let mut validator = X86FixItValidator::new();
let h1 =
X86FixItHint::new_replacement(8, "test.c", 1, 1, 1, 1, "", "", X86FixItCategory::Other);
let h2 = X86FixItHint::new_replacement(
9,
"test.c",
100,
1,
100,
1,
"",
"",
X86FixItCategory::Other,
);
let mut sources = HashMap::new();
sources.insert("test.c".to_string(), "hello".to_string());
let valid = validator.validate_all(&[h1, h2], &sources);
assert_eq!(valid.len(), 1);
}
#[test]
fn test_validator_clear() {
let mut validator = X86FixItValidator::new();
let hint = X86FixItHint::new_replacement(
10,
"test.c",
1,
1,
1,
1,
"",
"",
X86FixItCategory::Other,
);
let mut sources = HashMap::new();
sources.insert("test.c".to_string(), "hello".to_string());
validator.validate(&hint, &sources);
assert!(validator.validated_count() > 0);
validator.clear();
assert_eq!(validator.validated_count(), 0);
}
#[test]
fn test_pipeline_new() {
let pipeline = X86FixItPipeline::new();
assert!(pipeline.engine.hints().is_empty());
}
#[test]
fn test_pipeline_run() {
let mut pipeline = X86FixItPipeline::new();
pipeline.add_source("test.c", "int main() { printf(\"hi\"); }");
let result = pipeline.run("test.c");
assert!(result.is_ok());
let r = result.unwrap();
assert!(r.hints_generated > 0);
assert_eq!(r.file, "test.c");
}
#[test]
fn test_pipeline_run_missing_file() {
let mut pipeline = X86FixItPipeline::new();
let result = pipeline.run("missing.c");
assert!(result.is_err());
}
#[test]
fn test_pipeline_preview() {
let mut pipeline = X86FixItPipeline::new();
pipeline.add_source("test.c", "int main() { printf(\"hi\"); }");
let preview = pipeline.preview("test.c");
assert!(preview.is_ok());
let p = preview.unwrap();
assert!(p.contains("test.c"));
assert!(p.contains("Hints generated"));
assert!(p.contains("Diff Preview"));
}
#[test]
fn test_pipeline_reset() {
let mut pipeline = X86FixItPipeline::new();
pipeline.add_source("test.c", "int x");
pipeline.engine.suggest_missing_semicolon("test.c", 1, 6);
assert!(!pipeline.engine.hints().is_empty());
pipeline.reset();
assert!(pipeline.engine.hints().is_empty());
}
#[test]
fn test_metrics_compute() {
let h1 = X86FixItHint::new_replacement(
1,
"test.c",
1,
1,
1,
1,
"",
"",
X86FixItCategory::MissingSemicolon,
);
let h2 = X86FixItHint::new_replacement(
2,
"test.c",
2,
1,
2,
1,
"",
"",
X86FixItCategory::TypoCorrection,
)
.with_confidence(0.95);
let metrics = X86FixItMetrics::compute(&[h1, h2], 2, 0);
assert_eq!(metrics.total_hints, 2);
assert_eq!(metrics.high_confidence, 1);
assert_eq!(metrics.applied, 2);
}
#[test]
fn test_metrics_display() {
let metrics = X86FixItMetrics::new();
let display = metrics.display();
assert!(display.contains("Total hints"));
assert!(display.contains("Average confidence"));
assert!(display.contains("Category distribution"));
}
#[test]
fn test_metrics_new() {
let metrics = X86FixItMetrics::new();
assert_eq!(metrics.total_hints, 0);
assert_eq!(metrics.high_confidence, 0);
}
#[test]
fn test_integration_full_pipeline() {
let mut engine = X86FixItEngine::new();
let source = "#include <stdlib.h>\n\nint main() {\n prnitf(\"hello\");\n char buf[10];\n gets(buf);\n return 0;\n}";
engine.add_source("test.c", source);
engine.generate_all();
assert!(engine.hints().len() > 0);
}
#[test]
fn test_integration_typo_and_include() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "int main() { prnitf(\"hi\"); }");
let mut typos = vec![];
let mut includes = vec![];
let sources_map: HashMap<String, String> = [(
"test.c".to_string(),
"int main() { prnitf(\"hi\"); }".to_string(),
)]
.into_iter()
.collect();
engine
.typo_correction
.generate_all_hints(&mut typos, &sources_map);
engine
.missing_include
.generate_all_hints(&mut includes, &sources_map);
assert!(typos.len() + includes.len() > 0);
}
#[test]
fn test_integration_security_and_performance() {
let mut engine = X86FixItEngine::new();
engine.add_source(
"test.c",
"char buf[10]; gets(buf); for (int i = 0; i < N; i++) {}",
);
let mut sec_hints = vec![];
let mut perf_hints = vec![];
let sources_map: HashMap<String, String> = [(
"test.c".to_string(),
"char buf[10]; gets(buf); for (int i = 0; i < N; i++) {}".to_string(),
)]
.into_iter()
.collect();
engine
.security_fixit
.generate_all_hints(&mut sec_hints, &sources_map);
engine
.performance_fixit
.generate_all_hints(&mut perf_hints, &sources_map);
assert!(sec_hints.len() + perf_hints.len() > 0);
}
#[test]
fn test_integration_apply_then_verify() {
let mut engine = X86FixItEngine::new();
let hint = X86FixItHint::new_replacement(
999,
"test.c",
1,
1,
1,
6,
"float",
"change type",
X86FixItCategory::TypeConversion,
);
let result = engine.apply_replacement(&hint, "double x = 3.14;");
assert!(result.is_ok());
assert_eq!(result.unwrap(), "float x = 3.14;");
}
#[test]
fn test_integration_category_priority_ordering() {
let mut engine = X86FixItEngine::new();
engine.suggest_missing_semicolon("test.c", 3, 20); engine.suggest_typo_correction("test.c", 2, 1, 2, 5, "fix"); engine.suggest_nullptr("test.c", 1, 10, 1, 14);
let sorted = engine.hints_by_priority();
if !sorted.is_empty() {
assert_eq!(sorted[0].category, X86FixItCategory::MissingSemicolon);
}
}
#[test]
fn test_x86_fixit_kind_display() {
assert_eq!(format!("{}", X86FixItKind::Replacement), "replacement");
assert_eq!(format!("{}", X86FixItKind::Insertion), "insertion");
assert_eq!(format!("{}", X86FixItKind::Removal), "removal");
assert_eq!(format!("{}", X86FixItKind::Composition), "composition");
}
#[test]
fn test_x86_safe_replacement_fields() {
let repl = SafeReplacement {
function: "fgets".into(),
extra_args: Some("sizeof(buf), stdin".into()),
description: "safe replacement".into(),
severity: 10,
};
assert_eq!(repl.function, "fgets");
assert_eq!(repl.severity, 10);
}
#[test]
fn test_x86_pipeline_result_fields() {
let diff = X86FixItDiff {
file: "x.c".into(),
hunks: vec![],
};
let result = PipelineResult {
file: "x.c".into(),
original: "orig".into(),
modified: "mod".into(),
diff,
hints_generated: 5,
hints_applied: 3,
hints_rejected: 2,
validation_errors: 0,
};
assert_eq!(result.hints_generated, 5);
assert_eq!(result.hints_applied, 3);
assert_eq!(result.hints_rejected, 2);
}
#[test]
fn test_x86_stats_integration() {
let mut engine = X86FixItEngine::new();
engine.add_source("test.c", "int x");
engine.suggest_missing_semicolon("test.c", 1, 6);
assert!(engine.stats.total_generated > 0);
assert!(engine.stats.syntax_hints > 0);
}
#[test]
fn test_x86_data_model_sizeof_size_t() {
assert_eq!(X86DataModel::LP64.sizeof_size_t(), 8);
assert_eq!(X86DataModel::ILP32.sizeof_size_t(), 4);
assert_eq!(X86DataModel::LLP64.sizeof_size_t(), 8);
}
#[test]
fn test_x86_fixit_hint_with_meta() {
let hint = X86FixItHint::new_replacement(
9999,
"test.c",
1,
1,
1,
1,
"",
"",
X86FixItCategory::Other,
)
.with_meta("key1", "value1")
.with_meta("key2", "value2");
assert_eq!(hint.metadata.get("key1"), Some(&"value1".to_string()));
assert_eq!(hint.metadata.get("key2"), Some(&"value2".to_string()));
}
}