Skip to content

Commit

Permalink
feat: search current working directory for project file if not specif…
Browse files Browse the repository at this point in the history
…ied (#69)

Co-authored-by: Eva Ditzelmüller <87754804+EvaDitzelmueller@users.noreply.github.com>
  • Loading branch information
Flash0ver and EvaDitzelmueller authored Oct 29, 2023
1 parent ba90207 commit e5a9937
Show file tree
Hide file tree
Showing 12 changed files with 245 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FlashOWare.Tool.Cli.CodeAnalysis;
using FlashOWare.Tool.Cli.IO;
using FlashOWare.Tool.Core.UsingDirectives;
using Microsoft.CodeAnalysis;
using System.CommandLine.IO;
Expand All @@ -8,13 +9,13 @@ namespace FlashOWare.Tool.Cli;

public static partial class CliApplication
{
private static void AddUsingCommand(RootCommand rootCommand, MSBuildWorkspace workspace)
private static void AddUsingCommand(RootCommand rootCommand, MSBuildWorkspace workspace, IFileSystemAccessor fileSystem)
{
var usingCommand = new Command("using", " Analyze or refactor C# using directives.");
var countCommand = new Command("count", "Count and list all top-level using directives of a C# project.");
var globalizeCommand = new Command("globalize", "Move a top-level using directive to a global using directive in a C# project.");

var projectOption = new Option<FileInfo>(new[] { "--project", "--proj" }, "The project file to operate on.")
var projectOption = new Option<FileInfo>(new[] { "--project", "--proj" }, "The path to the project file to operate on (defaults to the current directory if there is only one project).")
.ExistingOnly();

countCommand.Add(projectOption);
Expand All @@ -23,7 +24,15 @@ private static void AddUsingCommand(RootCommand rootCommand, MSBuildWorkspace wo
FileInfo? project = context.ParseResult.GetValueForOption(projectOption);
if (project is null)
{
throw new NotImplementedException("Please pass a specific project path via --project.");
var currentDirectory = fileSystem.GetCurrentDirectory();
var files = currentDirectory.GetFiles("*.*proj");

project = files switch
{
[] => throw new InvalidOperationException("Specify a project file. The current working directory does not contain a project file."),
[var file] => file,
[..] => throw new InvalidOperationException("Specify which project file to use because this folder contains more than one project file."),
};
}

await CountUsingsAsync(workspace, project.FullName, context.Console, context.GetCancellationToken());
Expand All @@ -38,7 +47,15 @@ private static void AddUsingCommand(RootCommand rootCommand, MSBuildWorkspace wo
FileInfo? project = context.ParseResult.GetValueForOption(projectOption);
if (project is null)
{
throw new NotImplementedException("Please pass a specific project path via --project.");
var currentDirectory = fileSystem.GetCurrentDirectory();
var files = currentDirectory.GetFiles("*.*proj");

project = files switch
{
[] => throw new InvalidOperationException("Specify a project file. The current working directory does not contain a project file."),
[var file] => file,
[..] => throw new InvalidOperationException("Specify which project file to use because this folder contains more than one project file."),
};
}

await GlobalizeUsingsAsync(workspace, project.FullName, localUsing, context.Console, context.GetCancellationToken());
Expand Down
6 changes: 4 additions & 2 deletions src/libraries/FlashOWare.Tool.Cli/CliApplication.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FlashOWare.Tool.Cli.CommandLine;
using FlashOWare.Tool.Cli.IO;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
Expand All @@ -9,9 +10,10 @@ public static partial class CliApplication
{
private static readonly SemaphoreSlim s_msBuildMutex = new(1, 1);

public static async Task<int> RunAsync(string[] args, IConsole? console = null, VisualStudioInstance? msBuild = null)
public static async Task<int> RunAsync(string[] args, IConsole? console = null, VisualStudioInstance? msBuild = null, IFileSystemAccessor? fileSystem = null)
{
msBuild ??= MSBuildLocator.RegisterDefaults();
fileSystem ??= FileSystemAccessor.System;

var properties = ImmutableDictionary<string, string>.Empty.Add("Configuration", "Release");
using var workspace = MSBuildWorkspace.Create(properties);
Expand Down Expand Up @@ -40,7 +42,7 @@ public static async Task<int> RunAsync(string[] args, IConsole? console = null,
}
});

AddUsingCommand(rootCommand, workspace);
AddUsingCommand(rootCommand, workspace, fileSystem);

int exitCode = await rootCommand.InvokeAsync(args, console);
workspace.WorkspaceFailed -= OnWorkspaceFailed;
Expand Down
16 changes: 16 additions & 0 deletions src/libraries/FlashOWare.Tool.Cli/IO/FileSystemAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace FlashOWare.Tool.Cli.IO;

internal sealed class FileSystemAccessor : IFileSystemAccessor
{
public static FileSystemAccessor System { get; } = new FileSystemAccessor();

private FileSystemAccessor()
{
}

public DirectoryInfo GetCurrentDirectory()
{
string currentDirectory = Directory.GetCurrentDirectory();
return new DirectoryInfo(currentDirectory);
}
}
6 changes: 6 additions & 0 deletions src/libraries/FlashOWare.Tool.Cli/IO/IFileSystemAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace FlashOWare.Tool.Cli.IO;

public interface IFileSystemAccessor
{
DirectoryInfo GetCurrentDirectory();
}
18 changes: 18 additions & 0 deletions src/tests/FlashOWare.Tool.Cli.Tests/IO/FileSystemAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using FlashOWare.Tool.Cli.IO;

namespace FlashOWare.Tool.Cli.Tests.IO;

internal sealed class FileSystemAccessor : IFileSystemAccessor
{
private readonly DirectoryInfo _directory;

public FileSystemAccessor(DirectoryInfo directory)
{
_directory = directory;
}

public DirectoryInfo GetCurrentDirectory()
{
return _directory;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using FlashOWare.Tool.Cli.Tests.IO;
using FlashOWare.Tool.Cli.Tests.Workspaces;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using System.CommandLine.IO;
using Xunit.Abstractions;

Expand All @@ -13,6 +12,7 @@ public abstract class IntegrationTests : IDisposable
private static int s_number = 0;

private readonly DirectoryInfo _scratch;
private readonly FileSystemAccessor _fileSystem;
private readonly RedirectedConsole _system;

protected IntegrationTests()
Expand All @@ -31,6 +31,7 @@ protected IntegrationTests(ITestOutputHelper? output)
string name = type.FullName ?? type.Name;

_scratch = FileSystemUtilities.CreateScratchDirectory(Build.Configuration, Build.TFM, name, incremented);
_fileSystem = new FileSystemAccessor(_scratch);
Workspace = new PhysicalWorkspaceProvider(_scratch);

Result = new RunResult();
Expand All @@ -50,7 +51,7 @@ protected IntegrationTests(ITestOutputHelper? output)

protected async Task RunAsync(params string[] args)
{
int exitCode = await CliApplication.RunAsync(args, Console, MSBuild);
int exitCode = await CliApplication.RunAsync(args, Console, MSBuild, _fileSystem);

Result.Set(exitCode);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ internal class MyClass2
}

[Fact]
public async Task Count_ProjectFileDoesNotExist_FailsValidation()
public async Task Count_ExplicitProjectFileDoesNotExist_FailsValidation()
{
//Arrange
string project = "ProjectFileDoesNotExist.csproj";
Expand Down Expand Up @@ -102,4 +102,58 @@ End Class
Console.VerifyContains(null, $"Cannot open project '{project.File}' because the language '{LanguageNames.VisualBasic}' is not supported.");
Result.Verify(ExitCodes.Error);
}

[Fact]
public async Task Count_ImplicitSingleProject_UseCurrentDirectory()
{
//Arrange
_ = Workspace.CreateProject()
.AddDocument("""
using System;
namespace ProjectUnderTest.NetCore;
internal class MyClass1
{
}
""", "MyClass1")
.Initialize(ProjectKind.SdkStyle, TargetFramework.Net60, LanguageVersion.CSharp10);
string[] args = new[] { "using", "count" };
//Act
await RunAsync(args);
//Assert
Console.Verify($"""
Project: {Names.Project}
System: 1
""");
Result.Verify(ExitCodes.Success);
}

[Fact]
public async Task Count_ImplicitProjectMissing_Error()
{
//Arrange
string[] args = new[] { "using", "count" };
//Act
await RunAsync(args);
//Assert
Console.VerifyContains(null, "Specify a project file. The current working directory does not contain a project file.");
Result.Verify(ExitCodes.Error);
}

[Fact]
public async Task Count_ImplicitMultipleProjects_Ambiguous()
{
//Arrange
_ = Workspace.CreateProject()
.Initialize(ProjectKind.SdkStyle, TargetFramework.Net60, LanguageVersion.CSharp10);
_ = Workspace.CreateProject().WithProjectName("Ambiguous")
.Initialize(ProjectKind.SdkStyle, TargetFramework.Net60, LanguageVersion.CSharp10);
string[] args = new[] { "using", "count" };
//Act
await RunAsync(args);
//Assert
Console.VerifyContains(null, "Specify which project file to use because this folder contains more than one project file.");
Result.Verify(ExitCodes.Error);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ internal class MyClass2
}

[Fact]
public async Task Globalize_ProjectFileDoesNotExist_FailsValidation()
public async Task Globalize_ExplicitProjectFileDoesNotExist_FailsValidation()
{
//Arrange
string project = "ProjectFileDoesNotExist.csproj";
Expand Down Expand Up @@ -270,4 +270,75 @@ End Class
Console.VerifyContains(null, $"Cannot open project '{project.File}' because the language '{LanguageNames.VisualBasic}' is not supported.");
Result.Verify(ExitCodes.Error);
}

[Fact]
public async Task Globalize_ImplicitSingleProject_UseCurrentDirectory()
{
//Arrange
_ = Workspace.CreateProject()
.AddDocument("""
using System;
using System.Collections.Generic;
namespace ProjectUnderTest.NetCore;
internal class MyClass1
{
}
""", "MyClass1")
.Initialize(ProjectKind.SdkStyle, TargetFramework.Net60, LanguageVersion.CSharp10);
string[] args = new[] { "using", "globalize", Usings.System };
//Act
await RunAsync(args);
//Assert
Console.Verify($"""
Project: {Names.Project}
1 occurrence of Using Directive "System" was globalized to "GlobalUsings.cs".
""");
Workspace.CreateExpectation()
.AppendFile("""
global using System;
""", Names.GlobalUsings)
.AppendFile("""
using System.Collections.Generic;
namespace ProjectUnderTest.NetCore;
internal class MyClass1
{
}
""", "MyClass1.cs")
.AppendFile(ProjectText.Create(TargetFramework.Net60, LanguageVersion.CSharp10), Names.CSharpProject)
.Verify();
Result.Verify(ExitCodes.Success);
}

[Fact]
public async Task Globalize_ImplicitProjectMissing_Error()
{
//Arrange
string[] args = new[] { "using", "globalize", Usings.System };
//Act
await RunAsync(args);
//Assert
Console.VerifyContains(null, "Specify a project file. The current working directory does not contain a project file.");
Result.Verify(ExitCodes.Error);
}

[Fact]
public async Task Globalize_ImplicitMultipleProjects_Ambiguous()
{
//Arrange
_ = Workspace.CreateProject()
.Initialize(ProjectKind.SdkStyle, TargetFramework.Net60, LanguageVersion.CSharp10);
_ = Workspace.CreateProject().WithProjectName("Ambiguous")
.Initialize(ProjectKind.SdkStyle, TargetFramework.Net60, LanguageVersion.CSharp10);
string[] args = new[] { "using", "globalize", Usings.System };
//Act
await RunAsync(args);
//Assert
Console.VerifyContains(null, "Specify which project file to use because this folder contains more than one project file.");
Result.Verify(ExitCodes.Error);
}
}
28 changes: 24 additions & 4 deletions src/tests/FlashOWare.Tool.Cli.Tests/Workspaces/PhysicalDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,28 @@ namespace FlashOWare.Tool.Cli.Tests.Workspaces;

internal sealed class PhysicalDocument
{
public PhysicalDocument(string text, string directory, string file)
private PhysicalDocument(string text, string directory, string fileName)
: this(text, new DirectoryInfo(directory), fileName)
{
}

private PhysicalDocument(string text, DirectoryInfo directory, string fileName)
{
Text = text;
Directory = directory;
FullName = Path.Combine(directory, file);
FullName = Path.Combine(directory.FullName, fileName);
}

public string Text { get; }
public string Directory { get; }
public DirectoryInfo Directory { get; }
public string FullName { get; }

public static PhysicalDocument Create(string text, DirectoryInfo directory, string fileName, Language language)
{
string extension = language.GetDocumentExtension(true);
fileName = PathUtilities.WithExtension(extension, fileName);

return new PhysicalDocument(text, directory.FullName, fileName);
return new PhysicalDocument(text, directory, fileName);
}

public static PhysicalDocument Create(string text, DirectoryInfo directory, string fileName, string[] folders, Language language)
Expand All @@ -38,4 +43,19 @@ public static PhysicalDocument Create(string text, DirectoryInfo directory, stri

return new PhysicalDocument(text, folder, fileName);
}

public void Write()
{
if (!Directory.Exists)
{
Directory.Create();
}

if (File.Exists(FullName))
{
throw new InvalidOperationException($"Document '{FullName}' already exists.");
}

File.WriteAllText(FullName, Text);
}
}
18 changes: 18 additions & 0 deletions src/tests/FlashOWare.Tool.Cli.Tests/Workspaces/PhysicalProject.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using FlashOWare.Tool.Cli.Tests.IO;
using FlashOWare.Tool.Cli.Tests.Testing;
using System.Diagnostics;

namespace FlashOWare.Tool.Cli.Tests.Workspaces;

Expand All @@ -20,4 +21,21 @@ public static PhysicalProject Create(DirectoryInfo directory, string name, Langu
string path = Path.Combine(directory.FullName, fileName);
return new PhysicalProject(path);
}

public string GetDirectoryName()
{
string? directory = File.DirectoryName;
Debug.Assert(directory is not null, $"'{File}' is in a root directory.");
return directory;
}

public void Write(string text)
{
if (File.Exists)
{
throw new InvalidOperationException($"Project '{File}' already exists.");
}

System.IO.File.WriteAllText(File.FullName, text);
}
}
Loading

0 comments on commit e5a9937

Please sign in to comment.