Skip to content

Commit

Permalink
Merge pull request #59 from kysect/feat/format-with-custom-editorconfig
Browse files Browse the repository at this point in the history
Implement .editorconfig applying and result comparing
  • Loading branch information
FrediKats authored Sep 8, 2023
2 parents e7306b9 + c7d78ee commit 3b9c662
Show file tree
Hide file tree
Showing 15 changed files with 167 additions and 48 deletions.
53 changes: 42 additions & 11 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 между ними.
Expand All @@ -57,14 +99,3 @@ Configuin анализирует editorconfig файл и:
- Генерирует список существующих правил, которые не указаны в .editorconfig

### [in progress] Форматирование editorconfig'а

### [in progress] Предпросмотр изменений от editorconfig'а

Configuin генерирует список изменений, который будут получены, если к проекту применить .editorconfig.

Алгоритм:
- Запускается `dotnet format` для проекта и сохраняется список сообщений
- Подменяется .editorconfig на то, который указал пользователь
- Запускается `dotnet format` и сохраняется второй результат
- Генерируется diff между результатами
- Откатывается изменение .editorconfig'а
34 changes: 33 additions & 1 deletion Sources/Kysect.Configuin.Console/ConfiguinCommands.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -35,4 +42,29 @@ public void GenerateCodeStyle()
CodeStyle codeStyle = codeStyleGenerator.Generate(editorConfigSettings, roslynRules);
codeStyleWriter.Write(codeStyle);
}

public void GetEditorConfigWarningUpdates()
{
EditorConfigApplyConfiguration editorConfigApplyConfiguration = _serviceProvider.GetRequiredService<IOptions<EditorConfigApplyConfiguration>>().Value;
DotnetFormatWarningGenerator dotnetFormatWarningGenerator = _serviceProvider.GetRequiredService<DotnetFormatWarningGenerator>();
TemporaryFileMover temporaryFileMover = _serviceProvider.GetRequiredService<TemporaryFileMover>();
DotnetFormatReportComparator dotnetFormatReportComparator = _serviceProvider.GetRequiredService<DotnetFormatReportComparator>();
ILogger logger = _serviceProvider.GetRequiredService<ILogger>();

IReadOnlyCollection<DotnetFormatFileReport> originalWarnings = dotnetFormatWarningGenerator.GenerateWarnings(editorConfigApplyConfiguration.SolutionPath);
IReadOnlyCollection<DotnetFormatFileReport> newWarnings;

using (temporaryFileMover.MoveFile(editorConfigApplyConfiguration.NewEditorConfig, editorConfigApplyConfiguration.SourceEditorConfig))
newWarnings = dotnetFormatWarningGenerator.GenerateWarnings(editorConfigApplyConfiguration.SolutionPath);

CollectionDiff<DotnetFormatFileReport> 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}");
}
}
}
Original file line number Diff line number Diff line change
@@ -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!;
}
7 changes: 7 additions & 0 deletions Sources/Kysect.Configuin.Console/DependencyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,6 +27,7 @@ public static IServiceProvider InitializeServiceProvider()
IServiceCollection serviceCollection = builder.Services;

serviceCollection.AddOptionsWithValidation<ConfiguinConfiguration>(nameof(ConfiguinConfiguration));
serviceCollection.AddOptionsWithValidation<EditorConfigApplyConfiguration>(nameof(EditorConfigApplyConfiguration));
serviceCollection.AddSingleton(CreateLogger);

serviceCollection.AddSingleton<IEditorConfigContentProvider>(sp =>
Expand Down Expand Up @@ -57,6 +60,10 @@ public static IServiceProvider InitializeServiceProvider()
return new MarkdownCodeStyleWriter(options.Value.OutputPath, logger);
});

serviceCollection.AddSingleton<DotnetFormatWarningGenerator>();
serviceCollection.AddSingleton<TemporaryFileMover>();
serviceCollection.AddSingleton<DotnetFormatReportComparator>();

return serviceCollection.BuildServiceProvider();
}

Expand Down
3 changes: 2 additions & 1 deletion Sources/Kysect.Configuin.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
IServiceProvider s = DependencyBuilder.InitializeServiceProvider();
var configuinCommands = new ConfiguinCommands(s);

configuinCommands.GenerateCodeStyle();
//configuinCommands.GenerateCodeStyle();
configuinCommands.GetEditorConfigWarningUpdates();
23 changes: 10 additions & 13 deletions Sources/Kysect.Configuin.Core/CliExecution/CmdProcess.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Kysect.CommonLib.Logging;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Runtime.InteropServices;

Expand All @@ -25,18 +24,16 @@ 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;
IReadOnlyCollection<string> errors = GetErrors(process);
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();

Expand Down Expand Up @@ -76,12 +73,12 @@ private IReadOnlyCollection<string> 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;
}
Expand Down
5 changes: 3 additions & 2 deletions Sources/Kysect.Configuin.Core/DotnetFormat/DotnetFormatCli.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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}\"");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@ public DotnetFormatReportComparator(ILogger logger)

public CollectionDiff<DotnetFormatFileReport> 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<DotnetFormatFileReport> leftReports = JsonSerializer.Deserialize<IReadOnlyCollection<DotnetFormatFileReport>>(left).ThrowIfNull();
IReadOnlyCollection<DotnetFormatFileReport> rightReports = JsonSerializer.Deserialize<IReadOnlyCollection<DotnetFormatFileReport>>(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<DotnetFormatFileReport> Compare(IReadOnlyCollection<DotnetFormatFileReport> left, IReadOnlyCollection<DotnetFormatFileReport> 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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<DotnetFormatFileReport> 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<IReadOnlyCollection<DotnetFormatFileReport>>(warningFileContent).ThrowIfNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
namespace Kysect.Configuin.Core.FileSystem;

public interface IFileMoveUndoOperation
public interface IFileMoveUndoOperation : IDisposable
{
void Execute();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
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);

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
6 changes: 3 additions & 3 deletions Sources/Kysect.Configuin.Tests/TemporaryFileMoverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class TemporaryFileMoverTests

public TemporaryFileMoverTests()
{
_sut = new TemporaryFileMover(Path.Combine(TestGenerated, "TempDir"), TestLogger.ProviderForTests());
_sut = new TemporaryFileMover(TestLogger.ProviderForTests());
}

[SetUp]
Expand All @@ -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();
}

Expand All @@ -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);
Expand Down

0 comments on commit 3b9c662

Please sign in to comment.