using Microsoft.Extensions.Logging; using System.Collections.ObjectModel; using System.Text.RegularExpressions; namespace File_Folder_Helper.Helpers; internal static partial class HelperVSCodePossibleExtension { private record Method(string Name, int ParameterCount, int StartLine, int EndLine, int FirstUsedLine); [GeneratedRegex(@"(?[A-Z]{1}[A-Za-z_0-9]*)\(")] private static partial Regex CSharpMethodName(); [GeneratedRegex(@"\s[a-zA-Z_]*,")] private static partial Regex CSharpParameter(); [GeneratedRegex(@"\b(public|private|internal|protected)\s\b(static)?\s?\b(partial)?\s?\b(async)?\s?[[\]<,>?a-zA-Z()\s]*\s[A-Z]{1}[a-zA-Z_]+\(.*\)")] private static partial Regex CSharpMethodLine(); private static string? GetName(string line) { string? result; Match match = CSharpMethodName().Match(line); if (!match.Success) result = null; else result = match.Groups["method"].Value; return result; } private static int GetStartLine(string[] lines, int i) { int result = i; string line; for (int j = i - 1; j > -1; j--) { line = lines[j].Trim(); if (!line.StartsWith('[') && !line.StartsWith("/// ")) break; result--; } return result; } private static int GetParameterCount(string line, string search) { int result; string after = line.Split(search)[^1]; if (after.StartsWith(')')) result = 0; else { string[] segments = CSharpParameter().Split(after); result = segments.Length; } return result; } private static int GetLineBlockCount(string line, bool isLinq) { int result = 0; bool ignore = false; for (int i = 0; i < line.Length; i++) { if (line[i] == '\'') i++; else if (!isLinq && !ignore && line[i] == '{') result++; else if (!isLinq && !ignore && line[i] == '}') result--; else if (isLinq && !ignore && line[i] == ';') result--; else if (i > 0 && line[i] == '"' && line[i - 1] != '\\') ignore = !ignore; } return result; } private static int? GetFirstUsedLine(string[] lines, int i, string search, string searchNot, string searchWrap, int parameterCount) { int? result = null; string[] segments; string[] afterSegments; string lastSegmentBeforeDot; for (int j = 0; j < lines.Length; j++) { if (j == i) continue; segments = lines[j].Split(search); if (segments.Length == 1) { segments = lines[j].Split(searchNot); if (segments.Length == 1) { segments = lines[j].Split(searchWrap); if (segments.Length == 1) continue; } } lastSegmentBeforeDot = segments[^1].Split(").")[0]; if (parameterCount == 0) { if (lastSegmentBeforeDot.Contains(',')) continue; } else { afterSegments = lastSegmentBeforeDot.Split(','); if (afterSegments.Length != parameterCount) continue; } result = j; break; } return result; } private static ReadOnlyCollection GetMethodLines(ReadOnlyCollection methods) { List results = new(); foreach (Method method in methods) { for (int i = method.StartLine; i < method.EndLine + 1; i++) results.Add(i); } return new(results); } private static ReadOnlyCollection GetMethods(string cSharpFile, ILogger logger, string[] lines) { List results = new(); int blocks; bool isLinq; int endLine; string line; string? name; int startLine; string search; string innerLine; string searchNot; string searchWrap; int parameterCount; int? firstUsedLine; string lineSegmentFirst; for (int i = 0; i < lines.Length; i++) { line = lines[i].Trim(); if (string.IsNullOrEmpty(line)) continue; if (line.Length < 5) continue; if (line.EndsWith(',')) continue; if (!CSharpMethodLine().Match(line).Success) continue; name = GetName(line); search = $" {name}("; searchNot = $"!{name}("; searchWrap = $"({name}("; if (string.IsNullOrEmpty(name)) continue; blocks = 0; startLine = GetStartLine(lines, i); parameterCount = GetParameterCount(line, search); isLinq = lines[i + 1].Trim() != "{"; if (isLinq) blocks++; for (int j = i + 1; j < lines.Length; j++) { innerLine = lines[j].Trim(); if (isLinq && string.IsNullOrEmpty(innerLine)) { if (line.EndsWith(';')) blocks--; } blocks += GetLineBlockCount(innerLine, isLinq); if (blocks == 0) { endLine = j; if (lines.Length > j + 1 && string.IsNullOrEmpty(lines[j + 1].Trim())) endLine++; firstUsedLine = GetFirstUsedLine(lines, i, search, searchNot, searchWrap, parameterCount); if (firstUsedLine is null) { lineSegmentFirst = line.Split(search)[0]; if (!lines[i - 1].Trim().StartsWith("[Obsolete")) { if (lineSegmentFirst.StartsWith("private")) logger.LogWarning("<{cSharpFileName}> {name} with {parameterCount} parameter(s) <{line}>", Path.GetFileName(cSharpFile), name, parameterCount, lineSegmentFirst); else logger.LogInformation("<{cSharpFileName}> {name} with {parameterCount} parameter(s) <{line}>", Path.GetFileName(cSharpFile), name, parameterCount, lineSegmentFirst); } break; } if (j > lines.Length - 2) throw new Exception(); results.Add(new(name, parameterCount, startLine, endLine, firstUsedLine.Value)); break; } } } return new(results.OrderBy(l => l.FirstUsedLine).ToArray()); } private static bool WriteAllLines(string cSharpFile, string[] lines, ReadOnlyCollection methods) { bool result; List results = new(); ReadOnlyCollection methodLines = GetMethodLines(methods); int minMethodLines = methodLines.Min(); for (int i = 0; i < minMethodLines; i++) results.Add(lines[i]); foreach (Method method in methods) { for (int i = method.StartLine; i < method.EndLine + 1; i++) results.Add(lines[i]); } for (int i = minMethodLines; i < lines.Length; i++) { if (methodLines.Contains(i)) continue; results.Add(lines[i]); } string text = File.ReadAllText(cSharpFile); string join = string.Join(Environment.NewLine, results); if (join == text) result = false; else { result = true; File.WriteAllText(cSharpFile, join); } return result; } private static bool SortFile(ILogger logger, string cSharpFile, string[] lines) { bool result; ReadOnlyCollection methods = GetMethods(cSharpFile, logger, lines); if (methods.Count == 0) result = false; else result = WriteAllLines(cSharpFile, lines, methods); return result; } internal static void Sort(ILogger logger, List args) { bool result = false; bool check; string[] lines; long ticks = DateTime.Now.Ticks; logger.LogInformation("{ticks}", ticks); string[] cSharpFiles = Directory.GetFiles(args[0], "*.cs", SearchOption.TopDirectoryOnly); for (int i = 0; i < 10; i++) { foreach (string cSharpFile in cSharpFiles) { lines = File.ReadAllLines(cSharpFile); check = SortFile(logger, cSharpFile, lines); if (check && !result) result = true; } if (!result) break; } } }