diff --git a/SolutionParityChecker.App/ViewModels/MainWindowViewModel.cs b/SolutionParityChecker.App/ViewModels/MainWindowViewModel.cs index c56147a..acc706d 100644 --- a/SolutionParityChecker.App/ViewModels/MainWindowViewModel.cs +++ b/SolutionParityChecker.App/ViewModels/MainWindowViewModel.cs @@ -43,6 +43,12 @@ public partial class MainWindowViewModel : ViewModelBase ParityResults.Add(result); } } + + [RelayCommand] + private async Task FormatCsProjFile(CancellationToken token) + { + FormatCsproj.FormatCsprojFile(SolutionFilePath); + } [RelayCommand] private async Task LoadSolutionFile(CancellationToken token) diff --git a/SolutionParityChecker.App/Views/MainWindow.axaml b/SolutionParityChecker.App/Views/MainWindow.axaml index aac9435..4b0655f 100644 --- a/SolutionParityChecker.App/Views/MainWindow.axaml +++ b/SolutionParityChecker.App/Views/MainWindow.axaml @@ -19,6 +19,7 @@ + diff --git a/SolutionParityChecker.CLI/Commands/FormatCsprojCommand.cs b/SolutionParityChecker.CLI/Commands/FormatCsprojCommand.cs new file mode 100644 index 0000000..836aa7d --- /dev/null +++ b/SolutionParityChecker.CLI/Commands/FormatCsprojCommand.cs @@ -0,0 +1,26 @@ +using System.ComponentModel; +using Spectre.Console.Cli; + +namespace SolutionParityChecker.CLI.Commands; + +public class FormatCsprojCommand : Command +{ + public sealed class Settings : CommandSettings + { + [CommandArgument(1, "")] + public required string CsprojFilePath { get; set; } + + [CommandOption("-a|--enable-all")] + [Description("true to enable logging of all project files. Default is false.")] + [DefaultValue(false)] + public bool EnableAll { get; set; } = false; + } + + public override int Execute(CommandContext context, Settings settings) + { + var pathToCsprojFile = settings.CsprojFilePath; + Console.WriteLine($"Retrieving C# Project from {pathToCsprojFile}"); + FormatCsproj.FormatCsprojFile(pathToCsprojFile); + return 0; + } +} diff --git a/SolutionParityChecker.CLI/Commands/ImplicitUsingsCommand.cs b/SolutionParityChecker.CLI/Commands/ImplicitUsingsCommand.cs new file mode 100644 index 0000000..a2b137f --- /dev/null +++ b/SolutionParityChecker.CLI/Commands/ImplicitUsingsCommand.cs @@ -0,0 +1,83 @@ +using System.ComponentModel; +using Spectre.Console.Cli; + +namespace SolutionParityChecker.CLI.Commands; + +public class ImplicitUsingsCommand : Command +{ + public sealed class Settings : CommandSettings + { + [CommandArgument(1, "")] + public required string SolutionFilePath { get; set; } + + [CommandOption("-m|--add-missing")] + [Description("Add Implicit Usings=true to projects missing them. Default is false.")] + [DefaultValue(false)] + public bool AddMissing { get; set; } = false; + + [CommandOption("-d|--enable-disabled")] + [Description("Sets Implicit Usings to true for any projects with it disabled. Default is false.")] + [DefaultValue(false)] + public bool EnableDisabled { get; set; } = false; + + [CommandOption("-a|--enable-all")] + [Description("Enables Implicit Usings for all projects. Default is false.")] + [DefaultValue(false)] + public bool EnableAll { get; set; } = false; + } + + public override int Execute(CommandContext context, Settings settings) + { + var pathToSolutionFile = settings.SolutionFilePath; + Console.WriteLine($"Retrieving Solution from {pathToSolutionFile}"); + + var solutionFile = SolutionParityChecker.ParseSolutionFileFromPath(pathToSolutionFile); + if (solutionFile == null) + { + Console.WriteLine( + "Failed to parse solution file. The file was either not found or malformed." + ); + return 1; + } + var cSharpProjects = SolutionParityChecker.GetCSharpProjectObjectsFromSolutionFile(solutionFile); + Console.WriteLine($"Found {cSharpProjects.Count} C# Projects"); + Console.WriteLine("=================================================="); + + // Get the list of projects + var projectsMissingImplicitUsings = ImplicitUsings.FindCSharpProjectsMissingImplicitUsings(cSharpProjects); + + Console.WriteLine( + $"{projectsMissingImplicitUsings.Count} C# Projects have missing or disabled implicit usings" + ); + + foreach (var project in projectsMissingImplicitUsings) + { + Console.WriteLine(project.DirectoryPath); + } + + if (settings.AddMissing) + { + Console.WriteLine("=================================================="); + Console.WriteLine("Adding missing implicit usings"); + ImplicitUsings.AddMissingImplicitUsings(projectsMissingImplicitUsings); + var updatedProjects = SolutionParityChecker.GetCSharpProjectObjectsFromSolutionFile(solutionFile); + var projectsWithMissing = ImplicitUsings.FindCSharpProjectsMissingImplicitUsings(updatedProjects); + Console.WriteLine($"There are now {projectsWithMissing.Count} C# Projects missing/disabled implicit usings"); + } + if (settings.EnableDisabled) + { + Console.WriteLine("=================================================="); + Console.WriteLine("Enabling disabled implicit usings"); + ImplicitUsings.EnableDisabledImplicitUsings(projectsMissingImplicitUsings); + } + if (settings.EnableAll) + { + Console.WriteLine("=================================================="); + Console.WriteLine("Enabling all implicit usings"); + ImplicitUsings.EnableAllImplicitUsings(projectsMissingImplicitUsings); + } + Console.WriteLine("=================================================="); + Console.WriteLine("Done!"); + return 0; + } +} diff --git a/SolutionParityChecker.CLI/Program.cs b/SolutionParityChecker.CLI/Program.cs index d1ed55b..23effaa 100644 --- a/SolutionParityChecker.CLI/Program.cs +++ b/SolutionParityChecker.CLI/Program.cs @@ -8,6 +8,8 @@ app.Configure(config => config.ValidateExamples(); config.AddCommand("compare"); + config.AddCommand("implicit-usings"); + config.AddCommand("format-csproj"); }); return await app.RunAsync(args); diff --git a/SolutionParityChecker/FormatCsproj.cs b/SolutionParityChecker/FormatCsproj.cs new file mode 100644 index 0000000..8083fe4 --- /dev/null +++ b/SolutionParityChecker/FormatCsproj.cs @@ -0,0 +1,37 @@ +using System.Text; +using System.Xml; + +namespace SolutionParityChecker; + +public static class FormatCsproj +{ + public static void FormatCsprojFile(string csprojFilePath) + { + using TextReader rd = new StreamReader(csprojFilePath, Encoding.Default); + + XmlDocument doc = new XmlDocument(); + doc.Load(rd); + + if (rd != Console.In) + { + rd.Close(); + } + + using var wr = new StreamWriter(csprojFilePath, false, Encoding.Default); + + var settings = + new XmlWriterSettings + { + Indent = true, + IndentChars = "\t", + NewLineOnAttributes = false, + OmitXmlDeclaration = true + }; + + using (var writer = XmlWriter.Create(wr, settings)) + { + doc.WriteContentTo(writer); + writer.Close(); + } + } +} \ No newline at end of file diff --git a/SolutionParityChecker/ImplicitUsings.cs b/SolutionParityChecker/ImplicitUsings.cs new file mode 100644 index 0000000..d06021b --- /dev/null +++ b/SolutionParityChecker/ImplicitUsings.cs @@ -0,0 +1,60 @@ +using Microsoft.Build.Construction; + +namespace SolutionParityChecker; + +public static class ImplicitUsings +{ + public static List FindCSharpProjectsMissingImplicitUsings(List projectList) + { + var projectsMissingImplicitUsings = new List(); + + foreach (var project in projectList) + { + var implicitUsings = project.PropertyGroups + .SelectMany(x => x.Properties) + .FirstOrDefault(x => x.Name == "ImplicitUsings"); + if (implicitUsings is null || implicitUsings.Value is not "enable") + { + projectsMissingImplicitUsings.Add(project); + } + } + + return projectsMissingImplicitUsings; + } + + public static void AddMissingImplicitUsings(List projectsMissingImplicitUsings) + { + foreach (var project in projectsMissingImplicitUsings) + { + if (ProjectIsMissingImplicitUsings(project)) + { + project.AddProperty("ImplicitUsings", "enable"); + project.Save(); + FormatCsproj.FormatCsprojFile(project.FullPath); + } + } + } + + public static void EnableDisabledImplicitUsings(List projectsMissingImplicitUsings) + { + throw new NotImplementedException(); + } + + public static void EnableAllImplicitUsings(List projectsMissingImplicitUsings) + { + throw new NotImplementedException(); + } + + public static bool ProjectIsMissingImplicitUsings(ProjectRootElement project) + { + var implicitUsings = project.PropertyGroups + .SelectMany(x => x.Properties) + .FirstOrDefault(x => x.Name == "ImplicitUsings"); + if (implicitUsings is null) + { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/SolutionParityChecker/SolutionParityChecker.cs b/SolutionParityChecker/SolutionParityChecker.cs index 5f5e338..c71f4f2 100644 --- a/SolutionParityChecker/SolutionParityChecker.cs +++ b/SolutionParityChecker/SolutionParityChecker.cs @@ -60,4 +60,15 @@ public static class SolutionParityChecker return projectsMissingFromSolution; } + public static List GetCSharpProjectObjectsFromSolutionFile( + SolutionFile solutionFile + ) + { + var projectList = solutionFile.ProjectsByGuid + .Where(x => x.Value.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat) + .Select(s => ProjectRootElement.Open(s.Value.AbsolutePath)) + .ToList(); + + return projectList; + } }