diff --git a/Readme.md b/Readme.md index 17a6432..ef9b193 100644 --- a/Readme.md +++ b/Readme.md @@ -45,6 +45,48 @@ class MyClass \``` ``` +### Предпросмотр изменений от editorconfig'а + +Configuin генерирует список изменений, который будут получены, если к проекту применить .editorconfig. + +Алгоритм: +- Запускается `dotnet format` для проекта и сохраняется список сообщений +- Подменяется .editorconfig на то, который указал пользователь +- Запускается `dotnet format` и сохраняется второй результат +- Генерируется diff между результатами +- Откатывается изменение .editorconfig'а + +Пример использования: + +1. Берётся проект с .editorconfig'ом +2. Копируется .editorconfig и модифицируется, например, включается CA1032 +3. Запускается Configuin, указывается путь к солюшену и изменённому .editorconfig +4. Генерируется результат: + +```log +[18:24:58 INF] Generate dotnet format warnings for C:\Coding\Kysect.CommonLib\Sources\Kysect.CommonLib.sln will save to output-8b107f73-6763-4c1f-b643-4e8eabac9d91.json +[18:24:58 INF] Generate warnings for C:\Coding\Kysect.CommonLib\Sources\Kysect.CommonLib.sln and write result to output-8b107f73-6763-4c1f-b643-4e8eabac9d91.json +[18:24:58 VRB] Execute cmd command cmd.exe /C dotnet format "C:\Coding\Kysect.CommonLib\Sources\Kysect.CommonLib.sln" --verify-no-changes --report "output-8b107f73-6763-4c1f-b643-4e8eabac9d91.json" +[18:25:08 INF] Remove temp file output-8b107f73-6763-4c1f-b643-4e8eabac9d91.json +[18:25:08 INF] Move C:\Users\fredi\Desktop\.editorconfig to C:\Coding\Kysect.CommonLib\Sources\.editorconfig +[18:25:08 INF] Target path already exists. Save target file to temp path C:\Coding\Kysect.CommonLib\Sources\.congifuing\.editorconfig +[18:25:08 INF] Move C:\Coding\Kysect.CommonLib\Sources\.editorconfig to C:\Coding\Kysect.CommonLib\Sources\.congifuing\.editorconfig +[18:25:08 INF] Copy C:\Users\fredi\Desktop\.editorconfig to C:\Coding\Kysect.CommonLib\Sources\.editorconfig +[18:25:08 INF] Generate dotnet format warnings for C:\Coding\Kysect.CommonLib\Sources\Kysect.CommonLib.sln will save to output-4d210962-92ec-44c8-b367-35840f6403d9.json +[18:25:08 INF] Generate warnings for C:\Coding\Kysect.CommonLib\Sources\Kysect.CommonLib.sln and write result to output-4d210962-92ec-44c8-b367-35840f6403d9.json +[18:25:08 VRB] Execute cmd command cmd.exe /C dotnet format "C:\Coding\Kysect.CommonLib\Sources\Kysect.CommonLib.sln" --verify-no-changes --report "output-4d210962-92ec-44c8-b367-35840f6403d9.json" +[18:25:16 ERR] Cmd execution finished with exit code 2. +[18:25:16 INF] Remove temp file output-4d210962-92ec-44c8-b367-35840f6403d9.json +[18:25:16 INF] Undo file move. Move backup file from C:\Coding\Kysect.CommonLib\Sources\.congifuing\.editorconfig to C:\Coding\Kysect.CommonLib\Sources\.editorconfig +[18:25:16 INF] Comparing dotnet format report +[18:25:16 INF] Same: 0, added: 2, removed: 0 +[18:25:16 INF] New warnings count: 2 +[18:25:16 INF] C:\Coding\Kysect.CommonLib\Sources\Kysect.CommonLib\Reflection\ReflectionException.cs +[18:25:16 INF] error CA1032: Add the following constructor to ReflectionException: public ReflectionException() +[18:25:16 INF] C:\Coding\Kysect.CommonLib\Sources\Kysect.CommonLib\Reflection\ReflectionException.cs +[18:25:16 INF] error CA1032: Add the following constructor to ReflectionException: public ReflectionException(string message) +``` + ### [in progress] Сравнение двух editorconfig Configuin сравнивает два .editorconfig файла и генерирует diff между ними. @@ -57,14 +99,3 @@ Configuin анализирует editorconfig файл и: - Генерирует список существующих правил, которые не указаны в .editorconfig ### [in progress] Форматирование editorconfig'а - -### [in progress] Предпросмотр изменений от editorconfig'а - -Configuin генерирует список изменений, который будут получены, если к проекту применить .editorconfig. - -Алгоритм: -- Запускается `dotnet format` для проекта и сохраняется список сообщений -- Подменяется .editorconfig на то, который указал пользователь -- Запускается `dotnet format` и сохраняется второй результат -- Генерируется diff между результатами -- Откатывается изменение .editorconfig'а diff --git a/Sources/Kysect.Configuin.Console/ConfiguinCommands.cs b/Sources/Kysect.Configuin.Console/ConfiguinCommands.cs index 88f4f70..cf410b8 100644 --- a/Sources/Kysect.Configuin.Console/ConfiguinCommands.cs +++ b/Sources/Kysect.Configuin.Console/ConfiguinCommands.cs @@ -1,10 +1,17 @@ -using Kysect.Configuin.Core.CodeStyleGeneration; +using Kysect.CommonLib.Collections.Diff; +using Kysect.CommonLib.Logging; +using Kysect.Configuin.Console.Configuration; +using Kysect.Configuin.Core.CodeStyleGeneration; using Kysect.Configuin.Core.CodeStyleGeneration.Models; +using Kysect.Configuin.Core.DotnetFormat; using Kysect.Configuin.Core.EditorConfigParsing; +using Kysect.Configuin.Core.FileSystem; using Kysect.Configuin.Core.MsLearnDocumentation; using Kysect.Configuin.Core.MsLearnDocumentation.Models; using Kysect.Configuin.Core.RoslynRuleModels; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Kysect.Configuin.Console; @@ -35,4 +42,29 @@ public void GenerateCodeStyle() CodeStyle codeStyle = codeStyleGenerator.Generate(editorConfigSettings, roslynRules); codeStyleWriter.Write(codeStyle); } + + public void GetEditorConfigWarningUpdates() + { + EditorConfigApplyConfiguration editorConfigApplyConfiguration = _serviceProvider.GetRequiredService>().Value; + DotnetFormatWarningGenerator dotnetFormatWarningGenerator = _serviceProvider.GetRequiredService(); + TemporaryFileMover temporaryFileMover = _serviceProvider.GetRequiredService(); + DotnetFormatReportComparator dotnetFormatReportComparator = _serviceProvider.GetRequiredService(); + ILogger logger = _serviceProvider.GetRequiredService(); + + IReadOnlyCollection originalWarnings = dotnetFormatWarningGenerator.GenerateWarnings(editorConfigApplyConfiguration.SolutionPath); + IReadOnlyCollection newWarnings; + + using (temporaryFileMover.MoveFile(editorConfigApplyConfiguration.NewEditorConfig, editorConfigApplyConfiguration.SourceEditorConfig)) + newWarnings = dotnetFormatWarningGenerator.GenerateWarnings(editorConfigApplyConfiguration.SolutionPath); + + CollectionDiff warningDiff = dotnetFormatReportComparator.Compare(originalWarnings, newWarnings); + + logger.LogInformation($"New warnings count: {warningDiff.Added.Count}"); + foreach (DotnetFormatFileReport dotnetFormatFileReport in warningDiff.Added) + { + logger.LogTabInformation(1, $"{dotnetFormatFileReport.FilePath}"); + foreach (DotnetFormatFileChanges dotnetFormatFileChanges in dotnetFormatFileReport.FileChanges) + logger.LogTabInformation(2, $"{dotnetFormatFileChanges.FormatDescription}"); + } + } } \ No newline at end of file diff --git a/Sources/Kysect.Configuin.Console/Configuration/EditorConfigApplyConfiguration.cs b/Sources/Kysect.Configuin.Console/Configuration/EditorConfigApplyConfiguration.cs new file mode 100644 index 0000000..dae76a2 --- /dev/null +++ b/Sources/Kysect.Configuin.Console/Configuration/EditorConfigApplyConfiguration.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Kysect.Configuin.Console.Configuration; + +public class EditorConfigApplyConfiguration +{ + [Required] + public string SolutionPath { get; init; } = null!; + [Required] + public string SourceEditorConfig { get; init; } = null!; + [Required] + public string NewEditorConfig { get; init; } = null!; +} \ No newline at end of file diff --git a/Sources/Kysect.Configuin.Console/DependencyBuilder.cs b/Sources/Kysect.Configuin.Console/DependencyBuilder.cs index 1a3e080..4a1cdcd 100644 --- a/Sources/Kysect.Configuin.Console/DependencyBuilder.cs +++ b/Sources/Kysect.Configuin.Console/DependencyBuilder.cs @@ -2,7 +2,9 @@ using Kysect.Configuin.Console.Configuration; using Kysect.Configuin.Core.CodeStyleGeneration; using Kysect.Configuin.Core.CodeStyleGeneration.Markdown; +using Kysect.Configuin.Core.DotnetFormat; using Kysect.Configuin.Core.EditorConfigParsing; +using Kysect.Configuin.Core.FileSystem; using Kysect.Configuin.Core.MarkdownParsing.TextExtractor; using Kysect.Configuin.Core.MsLearnDocumentation; using Microsoft.Extensions.Configuration; @@ -25,6 +27,7 @@ public static IServiceProvider InitializeServiceProvider() IServiceCollection serviceCollection = builder.Services; serviceCollection.AddOptionsWithValidation(nameof(ConfiguinConfiguration)); + serviceCollection.AddOptionsWithValidation(nameof(EditorConfigApplyConfiguration)); serviceCollection.AddSingleton(CreateLogger); serviceCollection.AddSingleton(sp => @@ -57,6 +60,10 @@ public static IServiceProvider InitializeServiceProvider() return new MarkdownCodeStyleWriter(options.Value.OutputPath, logger); }); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + return serviceCollection.BuildServiceProvider(); } diff --git a/Sources/Kysect.Configuin.Console/Program.cs b/Sources/Kysect.Configuin.Console/Program.cs index d5ad16b..b1228bb 100644 --- a/Sources/Kysect.Configuin.Console/Program.cs +++ b/Sources/Kysect.Configuin.Console/Program.cs @@ -3,4 +3,5 @@ IServiceProvider s = DependencyBuilder.InitializeServiceProvider(); var configuinCommands = new ConfiguinCommands(s); -configuinCommands.GenerateCodeStyle(); \ No newline at end of file +//configuinCommands.GenerateCodeStyle(); +configuinCommands.GetEditorConfigWarningUpdates(); \ No newline at end of file diff --git a/Sources/Kysect.Configuin.Core/CliExecution/CmdProcess.cs b/Sources/Kysect.Configuin.Core/CliExecution/CmdProcess.cs index 23f7605..b27f16f 100644 --- a/Sources/Kysect.Configuin.Core/CliExecution/CmdProcess.cs +++ b/Sources/Kysect.Configuin.Core/CliExecution/CmdProcess.cs @@ -1,5 +1,4 @@ -using Kysect.CommonLib.Logging; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using System.Diagnostics; using System.Runtime.InteropServices; @@ -25,6 +24,8 @@ public CmdExecutionResult ExecuteCommand(string command) process.StartInfo = startInfo; process.Start(); + // TODO: hack. Without it process will waiting for someone read the stream or write it to parent terminal + process.StandardError.ReadToEnd(); process.WaitForExit(); int exitCode = process.ExitCode; @@ -32,11 +33,7 @@ public CmdExecutionResult ExecuteCommand(string command) var cmdExecutionResult = new CmdExecutionResult(exitCode, errors); if (cmdExecutionResult.IsAnyError()) - { - _logger.LogError("Finished with {exitCode} and {errorCount} errors.", exitCode, errors.Count); - foreach (string error in cmdExecutionResult.Errors) - _logger.LogTabError(1, error); - } + _logger.LogError("Cmd execution finished with exit code {exitCode}.", exitCode); process.Close(); @@ -76,12 +73,12 @@ private IReadOnlyCollection GetErrors(Process process) // TODO: fixed error stream reading // Line splitting triggered by char limit =_= - while (!process.StandardError.EndOfStream) - { - string? line = process.StandardError.ReadLine(); - if (line is not null) - errors.Add(line); - } + //while (!process.StandardError.EndOfStream) + //{ + // string? line = process.StandardError.ReadLine(); + // if (line is not null) + // errors.Add(line); + //} return errors; } diff --git a/Sources/Kysect.Configuin.Core/DotnetFormat/DotnetFormatCli.cs b/Sources/Kysect.Configuin.Core/DotnetFormat/DotnetFormatCli.cs index 47f7991..a4af8a8 100644 --- a/Sources/Kysect.Configuin.Core/DotnetFormat/DotnetFormatCli.cs +++ b/Sources/Kysect.Configuin.Core/DotnetFormat/DotnetFormatCli.cs @@ -20,9 +20,10 @@ public void Validate() _cmdProcess.ExecuteCommand("dotnet format -h").ThrowIfAnyError(); } - public void GenerateWarnings(string pathToSolution, string pathToJson) + public void Format(string pathToSolution, string pathToJson) { _logger.LogInformation("Generate warnings for {pathToSolution} and write result to {pathToJson}", pathToSolution, pathToJson); - _cmdProcess.ExecuteCommand($"dotnet format \"{pathToSolution}\" --verify-no-changes --report \"{pathToJson}\"").ThrowIfAnyError(); + // TODO: handle exceptions in some way? + _cmdProcess.ExecuteCommand($"dotnet format \"{pathToSolution}\" --verify-no-changes --report \"{pathToJson}\""); } } \ No newline at end of file diff --git a/Sources/Kysect.Configuin.Core/DotnetFormat/DotnetFormatReportComparator.cs b/Sources/Kysect.Configuin.Core/DotnetFormat/DotnetFormatReportComparator.cs index 610f743..685d9de 100644 --- a/Sources/Kysect.Configuin.Core/DotnetFormat/DotnetFormatReportComparator.cs +++ b/Sources/Kysect.Configuin.Core/DotnetFormat/DotnetFormatReportComparator.cs @@ -16,13 +16,19 @@ public DotnetFormatReportComparator(ILogger logger) public CollectionDiff Compare(string left, string right) { - _logger.LogInformation("Comparing dotnet format report between {left} and {right}", left, right); + _logger.LogInformation("Loading warnings for {left} and {right}", left, right); IReadOnlyCollection leftReports = JsonSerializer.Deserialize>(left).ThrowIfNull(); IReadOnlyCollection rightReports = JsonSerializer.Deserialize>(right).ThrowIfNull(); - var diff = CollectionDiff.Create(leftReports, rightReports, DotnetFormatFileReportComparator.Instance); - _logger.LogInformation("Same: {same}, added: {added}, removed: {removed}", diff.Same, diff.Added, diff.Removed); + return Compare(leftReports, rightReports); + } + + public CollectionDiff Compare(IReadOnlyCollection left, IReadOnlyCollection right) + { + _logger.LogInformation("Comparing dotnet format report"); + var diff = CollectionDiff.Create(left, right, DotnetFormatFileReportComparator.Instance); + _logger.LogInformation("Same: {same}, added: {added}, removed: {removed}", diff.Same.Count, diff.Added.Count, diff.Removed.Count); return diff; } diff --git a/Sources/Kysect.Configuin.Core/DotnetFormat/DotnetFormatWarningGenerator.cs b/Sources/Kysect.Configuin.Core/DotnetFormat/DotnetFormatWarningGenerator.cs new file mode 100644 index 0000000..e4ad026 --- /dev/null +++ b/Sources/Kysect.Configuin.Core/DotnetFormat/DotnetFormatWarningGenerator.cs @@ -0,0 +1,29 @@ +using Kysect.CommonLib.BaseTypes.Extensions; +using Microsoft.Extensions.Logging; +using System.Text.Json; + +namespace Kysect.Configuin.Core.DotnetFormat; + +public class DotnetFormatWarningGenerator +{ + private readonly ILogger _logger; + private readonly DotnetFormatCli _dotnetFormatCli; + + public DotnetFormatWarningGenerator(ILogger logger) + { + _logger = logger; + + _dotnetFormatCli = new DotnetFormatCli(logger); + } + + public IReadOnlyCollection GenerateWarnings(string pathToSolution) + { + string filePath = $"output-{Guid.NewGuid()}.json"; + _logger.LogInformation("Generate dotnet format warnings for {path} will save to {output}", pathToSolution, filePath); + _dotnetFormatCli.Format(pathToSolution, filePath); + string warningFileContent = File.ReadAllText(filePath); + _logger.LogInformation("Remove temp file {path}", filePath); + File.Delete(filePath); + return JsonSerializer.Deserialize>(warningFileContent).ThrowIfNull(); + } +} \ No newline at end of file diff --git a/Sources/Kysect.Configuin.Core/FileSystem/DeleteFileOperation.cs b/Sources/Kysect.Configuin.Core/FileSystem/DeleteFileOperation.cs index dba5a57..10338e2 100644 --- a/Sources/Kysect.Configuin.Core/FileSystem/DeleteFileOperation.cs +++ b/Sources/Kysect.Configuin.Core/FileSystem/DeleteFileOperation.cs @@ -13,7 +13,7 @@ public DeleteFileOperation(string path, ILogger logger) _logger = logger; } - public void Execute() + public void Dispose() { _logger.LogInformation("Undo file move. Delete {path}", _path); File.Delete(_path); diff --git a/Sources/Kysect.Configuin.Core/FileSystem/IFileMoveUndoOperation.cs b/Sources/Kysect.Configuin.Core/FileSystem/IFileMoveUndoOperation.cs index 725f501..b374838 100644 --- a/Sources/Kysect.Configuin.Core/FileSystem/IFileMoveUndoOperation.cs +++ b/Sources/Kysect.Configuin.Core/FileSystem/IFileMoveUndoOperation.cs @@ -1,6 +1,5 @@ namespace Kysect.Configuin.Core.FileSystem; -public interface IFileMoveUndoOperation +public interface IFileMoveUndoOperation : IDisposable { - void Execute(); } \ No newline at end of file diff --git a/Sources/Kysect.Configuin.Core/FileSystem/MoveBackFileOperation.cs b/Sources/Kysect.Configuin.Core/FileSystem/MoveBackFileOperation.cs index 504840f..9fc63e2 100644 --- a/Sources/Kysect.Configuin.Core/FileSystem/MoveBackFileOperation.cs +++ b/Sources/Kysect.Configuin.Core/FileSystem/MoveBackFileOperation.cs @@ -15,7 +15,7 @@ public MoveBackFileOperation(string source, string target, ILogger logger) _logger = logger; } - public void Execute() + public void Dispose() { _logger.LogInformation("Undo file move. Move backup file from {source} to {target}", _source, _target); File.Move(_source, _target, overwrite: true); diff --git a/Sources/Kysect.Configuin.Core/FileSystem/TemporaryFileReplacer.cs b/Sources/Kysect.Configuin.Core/FileSystem/TemporaryFileReplacer.cs index f81e47c..c5ffa44 100644 --- a/Sources/Kysect.Configuin.Core/FileSystem/TemporaryFileReplacer.cs +++ b/Sources/Kysect.Configuin.Core/FileSystem/TemporaryFileReplacer.cs @@ -1,23 +1,21 @@ -using Kysect.CommonLib.FileSystem.Extensions; +using Kysect.CommonLib.BaseTypes.Extensions; +using Kysect.CommonLib.FileSystem.Extensions; using Microsoft.Extensions.Logging; namespace Kysect.Configuin.Core.FileSystem; public class TemporaryFileMover { - private readonly string _tempDirectory; private readonly ILogger _logger; - public TemporaryFileMover(string tempDirectory, ILogger logger) + public TemporaryFileMover(ILogger logger) { - _tempDirectory = tempDirectory; _logger = logger; } public IFileMoveUndoOperation MoveFile(string sourcePath, string targetPath) { _logger.LogInformation("Move {sourcePath} to {targetPath}", sourcePath, targetPath); - DirectoryExtensions.EnsureFileExists(_tempDirectory); if (!File.Exists(sourcePath)) throw new FileNotFoundException(sourcePath); @@ -25,10 +23,14 @@ public IFileMoveUndoOperation MoveFile(string sourcePath, string targetPath) if (File.Exists(targetPath)) { var targetFileInfo = new FileInfo(targetPath); - string tempFilePath = Path.Combine(_tempDirectory, targetFileInfo.Name); + DirectoryInfo targetFileDirectory = targetFileInfo.Directory.ThrowIfNull(); + string tempFileDirectory = Path.Combine(targetFileDirectory.FullName, ".congifuing"); + DirectoryExtensions.EnsureFileExists(tempFileDirectory); + + string tempFilePath = Path.Combine(tempFileDirectory, targetFileInfo.Name); _logger.LogInformation("Target path already exists. Save target file to temp path {tempPath}", tempFilePath); _logger.LogInformation("Move {targetPath} to {tempFilePath}", targetPath, tempFilePath); - File.Move(targetPath, tempFilePath); + File.Move(targetPath, tempFilePath, overwrite: true); _logger.LogInformation("Copy {sourcePath} to {targetPath}", sourcePath, targetPath); File.Copy(sourcePath, targetPath, overwrite: true); return new MoveBackFileOperation(tempFilePath, targetPath, _logger); diff --git a/Sources/Kysect.Configuin.Tests/DotnetFormat/DotnetFormatCliTests.cs b/Sources/Kysect.Configuin.Tests/DotnetFormat/DotnetFormatCliTests.cs index 5ba0781..7b52b04 100644 --- a/Sources/Kysect.Configuin.Tests/DotnetFormat/DotnetFormatCliTests.cs +++ b/Sources/Kysect.Configuin.Tests/DotnetFormat/DotnetFormatCliTests.cs @@ -21,11 +21,12 @@ public void Validate_FinishedWithoutErrors() } [Test] - [Ignore("This test require infrastructure")] + //[Ignore("This test require infrastructure")] public void GenerateWarnings_CreateReportFile() { - const string pathToSln = "./../../../../"; + //const string pathToSln = "./../../../../"; + const string pathToSln = "C:\\Coding\\Kysect.PowerShellRunner\\Sources\\Kysect.PowerShellRunner.sln"; - _dotnetFormatCli.GenerateWarnings(pathToSln, "sample.json"); + _dotnetFormatCli.Format(pathToSln, "sample.json"); } } \ No newline at end of file diff --git a/Sources/Kysect.Configuin.Tests/TemporaryFileMoverTests.cs b/Sources/Kysect.Configuin.Tests/TemporaryFileMoverTests.cs index 18f6694..09135af 100644 --- a/Sources/Kysect.Configuin.Tests/TemporaryFileMoverTests.cs +++ b/Sources/Kysect.Configuin.Tests/TemporaryFileMoverTests.cs @@ -14,7 +14,7 @@ public class TemporaryFileMoverTests public TemporaryFileMoverTests() { - _sut = new TemporaryFileMover(Path.Combine(TestGenerated, "TempDir"), TestLogger.ProviderForTests()); + _sut = new TemporaryFileMover(TestLogger.ProviderForTests()); } [SetUp] @@ -38,7 +38,7 @@ public void MoveFile_WhenTargetNotExists_UndoDeleteFile() IFileMoveUndoOperation undoAction = _sut.MoveFile(fileForMove, targetPath); File.Exists(targetPath).Should().BeTrue(); - undoAction.Execute(); + undoAction.Dispose(); File.Exists(targetPath).Should().BeFalse(); } @@ -56,7 +56,7 @@ public void MoveFile_WhenTargetExists_UndoReturnOriginalFile() File.Exists(targetFile).Should().BeTrue(); File.ReadAllText(targetFile).Should().Be(fileForMove); - undoAction.Execute(); + undoAction.Dispose(); File.Exists(targetFile).Should().BeTrue(); File.ReadAllText(targetFile).Should().Be(targetFile);