using System.Text; using System.Text.RegularExpressions; using CodePlay.Core.Interfaces; using CodePlay.Core.Models; using CodePlay.Core.Common; namespace CodePlay.Core.Validators; public class AutoFixEngine : IAutoFixEngine { public Task FixAsync(string code, List errors, int round, CancellationToken cancellationToken = default) { var result = new FixResult { CanFix = false, RemainingErrors = new List() }; if (errors == null || errors.Count == 0) { result.CanFix = true; result.FixedCode = code; result.FixDescription = "No errors to fix"; return Task.FromResult(result); } result = round switch { 1 => FixRound1(code, errors), 2 => FixRound2(code, errors), 3 => FixRound3(code, errors), _ => new FixResult { RemainingErrors = errors } }; return Task.FromResult(result); } private FixResult FixRound1(string code, List errors) { var result = new FixResult { CanFix = false, RemainingErrors = new List(), FixDescription = string.Empty }; var needsSystemUsing = false; var needsCollectionsUsing = false; var needsLinqUsing = false; foreach (var error in errors) { if (error.ErrorId == "CS0246" || error.ErrorId == "CS0103") { if (error.Message.Contains("Console")) { needsSystemUsing = true; } else if (error.Message.Contains("List") || error.Message.Contains("Dictionary")) { needsCollectionsUsing = true; } else if (error.Message.Contains("LINQ") || error.Message.Contains("var")) { needsLinqUsing = true; } else { result.RemainingErrors.Add(error); } } else { result.RemainingErrors.Add(error); } } var fixedCode = code; var fixDescription = new StringBuilder(); if (needsSystemUsing && !code.Contains("using System;")) { fixedCode = fixedCode.Insert(0, "using System;\n"); fixDescription.Append("Added using System; "); } if (needsCollectionsUsing && !code.Contains("using System.Collections.Generic;")) { fixedCode = fixedCode.Insert(0, "using System.Collections.Generic;\n"); fixDescription.Append("Added using System.Collections.Generic;"); } if (needsLinqUsing && !code.Contains("using System.Linq;")) { fixedCode = fixedCode.Insert(0, "using System.Linq;\n"); fixDescription.Append("Added using System.Linq;"); } if (fixDescription.Length > 0) { result.CanFix = true; result.FixedCode = fixedCode; result.FixDescription = fixDescription.ToString().Trim(); } return result; } private FixResult FixRound2(string code, List errors) { var result = new FixResult { CanFix = false, RemainingErrors = new List(), FixDescription = string.Empty }; var fixedCode = code; var hasFixes = false; var fixDescription = new StringBuilder(); foreach (var error in errors) { if (error.ErrorId == "CS0246") { if (error.Message.Contains("String")) { fixedCode = fixedCode.Replace("String", "string"); hasFixes = true; fixDescription.Append("Mapped String to string; "); } else if (error.Message.Contains("ArrayList")) { fixedCode = fixedCode.Replace("ArrayList", "List"); hasFixes = true; fixDescription.Append("Mapped ArrayList to List; "); } else if (error.Message.Contains("HashMap")) { fixedCode = fixedCode.Replace("HashMap", "Dictionary"); hasFixes = true; fixDescription.Append("Mapped HashMap to Dictionary;"); } else { result.RemainingErrors.Add(error); } } else { result.RemainingErrors.Add(error); } } if (hasFixes) { result.CanFix = true; result.FixedCode = fixedCode; result.FixDescription = fixDescription.ToString().Trim(); } return result; } private FixResult FixRound3(string code, List errors) { var result = new FixResult { CanFix = false, RemainingErrors = new List(), FixDescription = string.Empty }; var fixedCode = code; var hasFixes = false; var fixDesc = new StringBuilder(); var remainingErrors = new List(); foreach (var error in errors) { var line = error.Line > 0 && error.Line <= code.Split('\n').Length ? code.Split('\n')[error.Line - 1].Trim() : ""; switch (error.ErrorId) { case "CS0117": hasFixes |= FixMissingMethod(ref fixedCode, error, line, fixDesc); break; case "CS1503": hasFixes |= FixArgumentMismatch(ref fixedCode, error, line, fixDesc); break; case "CS0234": if (error.Message.Contains("not found")) { var ns = Regex.Match(error.Message, @"'(.*?)'").Groups[1].Value; fixedCode = Regex.Replace(fixedCode, $@"using\s+{Regex.Escape(ns)}[\w.]*;", "// using removed: not found"); hasFixes = true; fixDesc.Append($"Removed unavailable namespace {ns}; "); } else { remainingErrors.Add(error); } break; case "CS1002": if (error.Line > 0) { var lines = fixedCode.Split('\n'); if (error.Line - 1 < lines.Length && !lines[error.Line - 1].TrimEnd().EndsWith(";")) { lines[error.Line - 1] = lines[error.Line - 1].TrimEnd() + ";"; fixedCode = string.Join("\n", lines); hasFixes = true; fixDesc.Append($"Added semicolon at line {error.Line}; "); } } break; case "CS1525": case "CS1003": hasFixes |= FixSyntaxError(ref fixedCode, error, line, fixDesc); break; default: remainingErrors.Add(error); break; } } result.RemainingErrors = remainingErrors; if (hasFixes) { result.CanFix = true; result.FixedCode = fixedCode; result.FixDescription = fixDesc.ToString().Trim(); } return result; } private bool FixMissingMethod(ref string code, CompilationError error, string line, StringBuilder desc) { if (error.Message.Contains("var")) { code = code.Replace(".var", ".var()"); desc.Append("Fixed .var to .var(); "); return true; } return false; } private bool FixArgumentMismatch(ref string code, CompilationError error, string line, StringBuilder desc) { var before = code; code = Regex.Replace(code, @"\.<[^>]+>\(", ".("); if (code != before) { desc.Append("Removed generic type arguments from method call; "); return true; } return false; } private bool FixSyntaxError(ref string code, CompilationError error, string line, StringBuilder desc) { var fixedAny = false; if (line.Contains(";;")) { code = code.Replace(";;", ";"); desc.Append("Fixed double semicolon; "); fixedAny = true; } if (line.Contains("( ")) { code = Regex.Replace(code, @"\(\s+", "("); desc.Append("Normalized spacing around parentheses; "); fixedAny = true; } return fixedAny; } }