Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ Both commands share the same basic arguments:

```bash
reference-switcher <command> \
--solution <path-to-sln-or-slnx> \
--scan-directory <directory-to-scan> \
[--solution <path-to-sln-or-slnx>] \
[--add-projects-to-solution]
```

- `--solution` (`-s`): path to the `.sln` or `.slnx` file that defines the starting projects.
- `--scan-directory` (`-d`): directory that will be scanned recursively for `.csproj` files used to build the project index.
- `--solution` (`-s`): path to the `.sln` or `.slnx` file that defines the starting projects. Optional when the current directory contains exactly one `.sln` or `.slnx` file.
- `--add-projects-to-solution`: when specified, the tool also updates the solution file to reflect the changes.

### Behavior of `--add-projects-to-solution`
Expand Down
6 changes: 6 additions & 0 deletions ReferenceSwitcher.Tool.Tests/ResultAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,10 @@ public static void Succeeded<T>(Result<T> result)
if (result.IsFailure)
Assert.Fail(result.Error);
}

public static void Failed<T>(Result<T> result)
{
if (result.IsSuccess)
Assert.Fail("Expected failure, but result succeeded.");
}
}
78 changes: 78 additions & 0 deletions ReferenceSwitcher.Tool.Tests/SolutionLocatorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
namespace ReferenceSwitcher.Tool.Tests;

public sealed class SolutionLocatorTests
{
[Fact]
public void UsesProvidedSolutionPath()
{
using var workspace = TestWorkspace.Create();
var solution = workspace.WriteFile("App.sln", "Microsoft Visual Studio Solution File");

var result = SolutionLocator.Resolve(solution, workspace.Root);

ResultAssertions.Succeeded(result);
Assert.Equal(Path.GetFullPath(solution), result.Value);
}

[Fact]
public void FindsSingleSlnInDirectory()
{
using var workspace = TestWorkspace.Create();
var solution = workspace.WriteFile("App.sln", "Microsoft Visual Studio Solution File");

var result = SolutionLocator.Resolve(null, workspace.Root);

ResultAssertions.Succeeded(result);
Assert.Equal(Path.GetFullPath(solution), result.Value);
}

[Fact]
public void FindsSingleSlnxInDirectory()
{
using var workspace = TestWorkspace.Create();
var solution = workspace.WriteFile("App.slnx", "<Solution />");

var result = SolutionLocator.Resolve(null, workspace.Root);

ResultAssertions.Succeeded(result);
Assert.Equal(Path.GetFullPath(solution), result.Value);
}

[Fact]
public void FailsWhenNoSolutionExists()
{
using var workspace = TestWorkspace.Create();

var result = SolutionLocator.Resolve(null, workspace.Root);

ResultAssertions.Failed(result);
Assert.Contains("--solution", result.Error);
}

[Fact]
public void FailsWhenMultipleSolutionsExist()
{
using var workspace = TestWorkspace.Create();
workspace.WriteFile("App.sln", "Microsoft Visual Studio Solution File");
workspace.WriteFile("Other.slnx", "<Solution />");

var result = SolutionLocator.Resolve(null, workspace.Root);

ResultAssertions.Failed(result);
Assert.Contains("--solution", result.Error);
Assert.Contains("more than one", result.Error);
}

[Fact]
public void PrefersProvidedPathEvenWhenDirectoryHasSolutions()
{
using var workspace = TestWorkspace.Create();
workspace.WriteFile("App.sln", "Microsoft Visual Studio Solution File");
var other = workspace.WriteFile("nested/Other.slnx", "<Solution />");

var result = SolutionLocator.Resolve(other, workspace.Root);

ResultAssertions.Succeeded(result);
Assert.Equal(Path.GetFullPath(other), result.Value);
}
}
18 changes: 12 additions & 6 deletions ReferenceSwitcher.Tool/ArgumentParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ internal static class ArgumentParser
{
public static RootCommand BuildRootCommand()
{
var solutionOption = new Option<FileInfo>("--solution", ["-s"])
var solutionOption = new Option<FileInfo?>("--solution", ["-s"])
{
Description = "Path to the base .sln or .slnx file.",
Required = true
Description = "Path to the base .sln or .slnx file. Optional when the current directory contains exactly one .sln or .slnx file."
};

var scanDirectoryOption = new Option<DirectoryInfo>("--scan-directory", ["-d"])
Expand Down Expand Up @@ -42,14 +41,21 @@ Command BuildCommand(string name, string description, SwitchMode mode)

command.SetAction(parseResult =>
{
var solutionFile = parseResult.GetValue(solutionOption);
var providedSolution = parseResult.GetValue(solutionOption);
var scanDirectory = parseResult.GetValue(scanDirectoryOption);
var updateSolution = parseResult.GetValue(updateSolutionOption);

ArgumentNullException.ThrowIfNull(solutionFile);
ArgumentNullException.ThrowIfNull(scanDirectory);

RunSwitch(mode, solutionFile, scanDirectory, updateSolution);
var solutionResult = SolutionLocator.Resolve(providedSolution?.FullName, Directory.GetCurrentDirectory());
if (solutionResult.IsFailure)
{
Console.Error.WriteLine(solutionResult.Error);
Environment.ExitCode = 1;
return;
}

RunSwitch(mode, new FileInfo(solutionResult.Value), scanDirectory, updateSolution);
});

return command;
Expand Down
31 changes: 31 additions & 0 deletions ReferenceSwitcher.Tool/SolutionLocator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using CSharpFunctionalExtensions;

namespace ReferenceSwitcher.Tool;

internal static class SolutionLocator
{
public static Result<string> Resolve(string? providedSolutionPath, string directory)
{
if (!string.IsNullOrWhiteSpace(providedSolutionPath))
return Result.Success(Path.GetFullPath(providedSolutionPath));

var solutions = Directory.EnumerateFiles(directory)
.Where(IsSolutionFile)
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
.ToArray();

return solutions.Length switch
{
1 => Result.Success(Path.GetFullPath(solutions[0])),
0 => Result.Failure<string>("Option '--solution' is required when the current directory does not contain a .sln or .slnx file."),
_ => Result.Failure<string>("Option '--solution' is required when the current directory contains more than one .sln or .slnx file.")
};
}

private static bool IsSolutionFile(string path)
{
var extension = Path.GetExtension(path);
return extension.Equals(".sln", StringComparison.OrdinalIgnoreCase)
|| extension.Equals(".slnx", StringComparison.OrdinalIgnoreCase);
}
}