using Microsoft.Extensions.Logging; using System.Collections.ObjectModel; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.RegularExpressions; namespace File_Folder_Helper.ADO2024.PI1; internal static partial class Helper20240108 { private record Method(int EndLine, string FirstLine, int FirstUsedLine, string Name, int ParameterCount, int StartLine); [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(Method[]))] private partial class MethodCollectionCommonSourceGenerationContext : JsonSerializerContext { } [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('#') && !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, string searchDelegate, string searchConstructor, 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) { if (!lines[j].EndsWith(searchDelegate)) { segments = lines[j].Split(searchConstructor); if (segments.Length == 1) continue; } } } } if (lines[j].EndsWith(searchDelegate)) { result = j; break; } else { 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 = []; 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 = []; int blocks; bool isLinq; int endLine; string line; string? name; int startLine; Method method; string search; string firstLine; string innerLine; string searchNot; string searchWrap; int parameterCount; int? firstUsedLine; string searchDelegate; string lineSegmentFirst; string searchConstructor; 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}("; searchDelegate = $" += {name};"; if (string.IsNullOrEmpty(name)) continue; blocks = 0; startLine = GetStartLine(lines, i); searchConstructor = $"{name.ToLower()} = new("; parameterCount = GetParameterCount(line, search); if (!lines[startLine].StartsWith("#pragma")) firstLine = lines[startLine].Trim(); else firstLine = lines[startLine + 1].Trim(); isLinq = !lines[i + 1].StartsWith("#pragma") && lines[i + 1].Trim() != "{"; if (isLinq) blocks++; for (int j = i + 1; j < lines.Length; j++) { innerLine = lines[j].Trim(); if (innerLine.StartsWith("#pragma")) continue; 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, searchDelegate, searchConstructor, 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(); method = new(endLine, firstLine, firstUsedLine.Value, name, parameterCount, startLine); results.Add(method); break; } } } return new(results.OrderBy(l => l.FirstUsedLine).ToArray()); } private static bool WriteAllLines(string cSharpFile, string[] lines, ReadOnlyCollection methods) { bool result; List results = []; if (methods.Count == 0) File.WriteAllText(".vscode/.json", JsonSerializer.Serialize(methods.ToArray(), MethodCollectionCommonSourceGenerationContext.Default.MethodArray)); 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 SortCodeMethods(ILogger logger, List args, CancellationToken cancellationToken) { bool result = false; bool check; string[] lines; bool usePathCombine = true; long ticks = DateTime.Now.Ticks; logger.LogInformation("{ticks}", ticks); string directory = Path.GetFullPath(args[2]); string repositoryDirectory = Path.GetFullPath(args[0]); string[] cSharpFiles = Directory.GetFiles(directory, "*.cs", SearchOption.AllDirectories); ReadOnlyCollection gitOthersModifiedAndDeletedExcludingStandardFiles = Helpers.HelperGit.GetOthersModifiedAndDeletedExcludingStandardFiles(repositoryDirectory, usePathCombine, cancellationToken); for (int i = 0; i < 10; i++) { foreach (string cSharpFile in cSharpFiles) { if (!gitOthersModifiedAndDeletedExcludingStandardFiles.Contains(cSharpFile)) continue; lines = File.ReadAllLines(cSharpFile); check = SortFile(logger, cSharpFile, lines); if (check && !result) result = true; } if (!result) break; } } }