mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05a70175cc | ||
|
|
33ec2eb3a0 | ||
|
|
f6ef6cd4c0 | ||
|
|
a9ef693dc1 | ||
|
|
98bbd666dc | ||
|
|
4e7ed830f8 | ||
|
|
ef87ff76fc | ||
|
|
2feeb21270 | ||
|
|
9990387cfa | ||
|
|
bc1bdca7c6 | ||
|
|
2a992d37df | ||
|
|
15c87aecbb | ||
|
|
10a46451ac | ||
|
|
e4c6a4174b | ||
|
|
4c65f7bbee | ||
|
|
5f21de0df5 | ||
|
|
9b01b67d98 | ||
|
|
4508f5e211 | ||
|
|
f0cbc46df4 | ||
|
|
6c96e9e173 |
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1,3 +0,0 @@
|
|||||||
github: Tyrrrz
|
|
||||||
patreon: Tyrrrz
|
|
||||||
custom: ['buymeacoffee.com/Tyrrrz', 'tyrrrz.me/donate']
|
|
||||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
|
- name: 💬 Discord server
|
||||||
|
url: https://discord.gg/2SUWKFnHSm
|
||||||
|
about: Chat with the project community.
|
||||||
- name: 🗨 Discussions
|
- name: 🗨 Discussions
|
||||||
url: https://github.com/Tyrrrz/CliFx/discussions/new
|
url: https://github.com/Tyrrrz/CliFx/discussions/new
|
||||||
about: Ask and answer questions.
|
about: Ask and answer questions.
|
||||||
17
.github/PULL_REQUEST_TEMPLATE.md
vendored
17
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,17 +0,0 @@
|
|||||||
<!--
|
|
||||||
|
|
||||||
**Important:**
|
|
||||||
|
|
||||||
Please make sure that there is an existing issue that describes the problem solved by your pull request. If there isn't one, consider creating it first.
|
|
||||||
An open issue offers a good place to iron out requirements, discuss possible solutions, and ask questions.
|
|
||||||
|
|
||||||
Remember to also:
|
|
||||||
|
|
||||||
- Keep your pull request focused and as small as possible. If you want to contribute multiple unrelated changes, please create separate pull requests for them.
|
|
||||||
- Follow the coding style and conventions already established by the project. When in doubt about which style to use, ask in the comments to your pull request.
|
|
||||||
- If you want to start a discussion regarding a specific change you've made, add a review comment to your own code. This can be used to highlight something important or to seek further input from others.
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- Please specify the issue addressed by this pull request -->
|
|
||||||
Closes #ISSUE_NUMBER
|
|
||||||
25
.github/workflows/CD.yml
vendored
25
.github/workflows/CD.yml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: CD
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "*"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2.3.3
|
|
||||||
|
|
||||||
- name: Install .NET
|
|
||||||
uses: actions/setup-dotnet@v1.7.2
|
|
||||||
with:
|
|
||||||
dotnet-version: 5.0.x
|
|
||||||
|
|
||||||
- name: Pack
|
|
||||||
run: dotnet pack CliFx --configuration Release
|
|
||||||
|
|
||||||
- name: Deploy
|
|
||||||
run: dotnet nuget push CliFx/bin/Release/*.nupkg -s nuget.org -k ${{ secrets.NUGET_TOKEN }}
|
|
||||||
28
.github/workflows/CI.yml
vendored
28
.github/workflows/CI.yml
vendored
@@ -1,28 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2.3.3
|
|
||||||
|
|
||||||
- name: Install .NET
|
|
||||||
uses: actions/setup-dotnet@v1.7.2
|
|
||||||
with:
|
|
||||||
dotnet-version: 5.0.x
|
|
||||||
|
|
||||||
- name: Build & test
|
|
||||||
run: dotnet test --configuration Release --logger GitHubActions
|
|
||||||
|
|
||||||
- name: Upload coverage
|
|
||||||
uses: codecov/codecov-action@v1.0.5
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
|
||||||
11
.github/workflows/main.yml
vendored
Normal file
11
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
name: main
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
main:
|
||||||
|
uses: Tyrrrz/.github/.github/workflows/NuGet.yml@master
|
||||||
|
secrets:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }}
|
||||||
|
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
|
### v2.1 (04-Jan-2022)
|
||||||
|
|
||||||
|
- Added `IConsole.Clear()` with corresponding implementations in `SystemConsole`, `FakeConsole`, and `FakeInMemoryConsole`. (Thanks [@Alex Rosenfeld](https://github.com/alexrosenfeld10))
|
||||||
|
- Added `IConsole.ReadKey()` with corresponding implementations in `SystemConsole`, `FakeConsole`, and `FakeInMemoryConsole`. (Thanks [@Alex Rosenfeld](https://github.com/alexrosenfeld10))
|
||||||
|
- Fixed an issue that caused parameters to appear out of order in the usage format section of the help text. (Thanks [@David Fallah](https://github.com/TAGC))
|
||||||
|
|
||||||
### v2.0.6 (17-Jul-2021)
|
### v2.0.6 (17-Jul-2021)
|
||||||
|
|
||||||
- Fixed an issue where an exception thrown via reflection during parameter or option binding resulted in `Exception has been thrown by the target of an invocation` error instead of a more useful message. Such exceptions will now be unwrapped to provide better user experience.
|
- Fixed an issue where an exception thrown via reflection during parameter or option binding resulted in `Exception has been thrown by the target of an invocation` error instead of a more useful message. Such exceptions will now be unwrapped to provide better user experience.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<IsTestProject>true</IsTestProject>
|
<IsTestProject>true</IsTestProject>
|
||||||
<CollectCoverage>true</CollectCoverage>
|
<CollectCoverage>true</CollectCoverage>
|
||||||
@@ -13,14 +13,14 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Basic.Reference.Assemblies" Version="1.1.2" />
|
<PackageReference Include="Basic.Reference.Assemblies" Version="1.2.4" />
|
||||||
<PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" />
|
<PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" />
|
||||||
<PackageReference Include="FluentAssertions" Version="5.10.3" />
|
<PackageReference Include="FluentAssertions" Version="6.2.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" PrivateAssets="all" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" PrivateAssets="all" />
|
||||||
<PackageReference Include="coverlet.msbuild" Version="3.0.3" PrivateAssets="all" />
|
<PackageReference Include="coverlet.msbuild" Version="3.1.0" PrivateAssets="all" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -2,71 +2,70 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class CommandMustBeAnnotatedAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new CommandMustBeAnnotatedAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class CommandMustBeAnnotatedAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_a_command_is_not_annotated_with_the_command_attribute()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new CommandMustBeAnnotatedAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_a_command_is_not_annotated_with_the_command_attribute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_a_command_is_annotated_with_the_command_attribute()
|
public void Analyzer_does_not_report_an_error_if_a_command_is_annotated_with_the_command_attribute()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public abstract class MyCommand : ICommand
|
public abstract class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_a_command_is_implemented_as_an_abstract_class()
|
public void Analyzer_does_not_report_an_error_if_a_command_is_implemented_as_an_abstract_class()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
public abstract class MyCommand : ICommand
|
public abstract class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_class_that_is_not_a_command()
|
public void Analyzer_does_not_report_an_error_on_a_class_that_is_not_a_command()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
public class Foo
|
public class Foo
|
||||||
{
|
{
|
||||||
public int Bar { get; set; } = 5;
|
public int Bar { get; set; } = 5;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,57 +2,56 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class CommandMustImplementInterfaceAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new CommandMustImplementInterfaceAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class CommandMustImplementInterfaceAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_a_command_does_not_implement_ICommand_interface()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new CommandMustImplementInterfaceAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_a_command_does_not_implement_ICommand_interface()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand
|
public class MyCommand
|
||||||
{
|
{
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_a_command_implements_ICommand_interface()
|
public void Analyzer_does_not_report_an_error_if_a_command_implements_ICommand_interface()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_class_that_is_not_a_command()
|
public void Analyzer_does_not_report_an_error_on_a_class_that_is_not_a_command()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
public class Foo
|
public class Foo
|
||||||
{
|
{
|
||||||
public int Bar { get; set; } = 5;
|
public int Bar { get; set; } = 5;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,28 +4,27 @@ using FluentAssertions;
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
|
|
||||||
|
public class GeneralSpecs
|
||||||
{
|
{
|
||||||
public class GeneralSpecs
|
[Fact]
|
||||||
|
public void All_analyzers_have_unique_diagnostic_IDs()
|
||||||
{
|
{
|
||||||
[Fact]
|
// Arrange
|
||||||
public void All_analyzers_have_unique_diagnostic_IDs()
|
var analyzers = typeof(AnalyzerBase)
|
||||||
{
|
.Assembly
|
||||||
// Arrange
|
.GetTypes()
|
||||||
var analyzers = typeof(AnalyzerBase)
|
.Where(t => !t.IsAbstract && t.IsAssignableTo(typeof(DiagnosticAnalyzer)))
|
||||||
.Assembly
|
.Select(t => (DiagnosticAnalyzer) Activator.CreateInstance(t)!)
|
||||||
.GetTypes()
|
.ToArray();
|
||||||
.Where(t => !t.IsAbstract && t.IsAssignableTo(typeof(DiagnosticAnalyzer)))
|
|
||||||
.Select(t => (DiagnosticAnalyzer) Activator.CreateInstance(t)!)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var diagnosticIds = analyzers
|
var diagnosticIds = analyzers
|
||||||
.SelectMany(a => a.SupportedDiagnostics.Select(d => d.Id))
|
.SelectMany(a => a.SupportedDiagnostics.Select(d => d.Id))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnosticIds.Should().OnlyHaveUniqueItems();
|
diagnosticIds.Should().OnlyHaveUniqueItems();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,34 +2,34 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class OptionMustBeInsideCommandAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustBeInsideCommandAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class OptionMustBeInsideCommandAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_an_option_is_inside_a_class_that_is_not_a_command()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustBeInsideCommandAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_an_option_is_inside_a_class_that_is_not_a_command()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
public class MyClass
|
public class MyClass
|
||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_an_option_is_inside_a_command()
|
public void Analyzer_does_not_report_an_error_if_an_option_is_inside_a_command()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -39,32 +39,32 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_an_option_is_inside_an_abstract_class()
|
public void Analyzer_does_not_report_an_error_if_an_option_is_inside_an_abstract_class()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
public abstract class MyCommand
|
public abstract class MyCommand
|
||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -73,8 +73,7 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,18 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class OptionMustHaveNameOrShortNameAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveNameOrShortNameAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class OptionMustHaveNameOrShortNameAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_an_option_does_not_have_a_name_or_short_name()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveNameOrShortNameAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_an_option_does_not_have_a_name_or_short_name()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -23,16 +23,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_an_option_has_a_name()
|
public void Analyzer_does_not_report_an_error_if_an_option_has_a_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -42,16 +42,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_an_option_has_a_short_name()
|
public void Analyzer_does_not_report_an_error_if_an_option_has_a_short_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -61,16 +61,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -79,8 +79,7 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,18 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class OptionMustHaveUniqueNameAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveUniqueNameAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class OptionMustHaveUniqueNameAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_an_option_has_the_same_name_as_another_option()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveUniqueNameAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_an_option_has_the_same_name_as_another_option()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -26,16 +26,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_an_option_has_a_unique_name()
|
public void Analyzer_does_not_report_an_error_if_an_option_has_a_unique_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -48,16 +48,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_name()
|
public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -67,16 +67,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -85,8 +85,7 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,18 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class OptionMustHaveUniqueShortNameAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveUniqueShortNameAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class OptionMustHaveUniqueShortNameAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_an_option_has_the_same_short_name_as_another_option()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveUniqueShortNameAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_an_option_has_the_same_short_name_as_another_option()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -26,16 +26,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_an_option_has_a_unique_short_name()
|
public void Analyzer_does_not_report_an_error_if_an_option_has_a_unique_short_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -48,16 +48,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_an_option_has_a_short_name_which_is_unique_only_in_casing()
|
public void Analyzer_does_not_report_an_error_if_an_option_has_a_short_name_which_is_unique_only_in_casing()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -70,16 +70,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_short_name()
|
public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_short_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -89,16 +89,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -107,8 +107,7 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,18 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class OptionMustHaveValidConverterAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidConverterAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class OptionMustHaveValidConverterAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_the_specified_option_converter_does_not_derive_from_BindingConverter()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidConverterAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_the_specified_option_converter_does_not_derive_from_BindingConverter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
public class MyConverter
|
public class MyConverter
|
||||||
{
|
{
|
||||||
public string Convert(string rawValue) => rawValue;
|
public string Convert(string rawValue) => rawValue;
|
||||||
@@ -28,16 +28,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_the_specified_option_converter_derives_from_BindingConverter()
|
public void Analyzer_does_not_report_an_error_if_the_specified_option_converter_derives_from_BindingConverter()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
public class MyConverter : BindingConverter<string>
|
public class MyConverter : BindingConverter<string>
|
||||||
{
|
{
|
||||||
public override string Convert(string rawValue) => rawValue;
|
public override string Convert(string rawValue) => rawValue;
|
||||||
@@ -52,16 +52,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_converter()
|
public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_converter()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -71,16 +71,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -89,8 +89,7 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,18 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class OptionMustHaveValidNameAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidNameAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class OptionMustHaveValidNameAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_an_option_has_a_name_which_is_too_short()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidNameAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_an_option_has_a_name_which_is_too_short()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -23,16 +23,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_reports_an_error_if_an_option_has_a_name_that_starts_with_a_non_letter_character()
|
public void Analyzer_reports_an_error_if_an_option_has_a_name_that_starts_with_a_non_letter_character()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -42,16 +42,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_an_option_has_a_valid_name()
|
public void Analyzer_does_not_report_an_error_if_an_option_has_a_valid_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -61,16 +61,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_name()
|
public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -80,16 +80,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -98,8 +98,7 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,18 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class OptionMustHaveValidShortNameAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidShortNameAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class OptionMustHaveValidShortNameAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_an_option_has_a_short_name_which_is_not_a_letter_character()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidShortNameAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_an_option_has_a_short_name_which_is_not_a_letter_character()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -23,16 +23,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_an_option_has_a_valid_short_name()
|
public void Analyzer_does_not_report_an_error_if_an_option_has_a_valid_short_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -42,16 +42,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_short_name()
|
public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_short_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -61,16 +61,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -79,8 +79,7 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,18 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class OptionMustHaveValidValidatorsAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidValidatorsAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class OptionMustHaveValidValidatorsAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_one_of_the_specified_option_validators_does_not_derive_from_BindingValidator()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidValidatorsAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_one_of_the_specified_option_validators_does_not_derive_from_BindingValidator()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
public class MyValidator
|
public class MyValidator
|
||||||
{
|
{
|
||||||
public void Validate(string value) {}
|
public void Validate(string value) {}
|
||||||
@@ -28,16 +28,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_all_specified_option_validators_derive_from_BindingValidator()
|
public void Analyzer_does_not_report_an_error_if_all_specified_option_validators_derive_from_BindingValidator()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
public class MyValidator : BindingValidator<string>
|
public class MyValidator : BindingValidator<string>
|
||||||
{
|
{
|
||||||
public override BindingValidationError Validate(string value) => Ok();
|
public override BindingValidationError Validate(string value) => Ok();
|
||||||
@@ -52,16 +52,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_validators()
|
public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_validators()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -71,16 +71,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -89,8 +89,7 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,34 +2,34 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class ParameterMustBeInsideCommandAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeInsideCommandAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class ParameterMustBeInsideCommandAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_a_parameter_is_inside_a_class_that_is_not_a_command()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeInsideCommandAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_a_parameter_is_inside_a_class_that_is_not_a_command()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
public class MyClass
|
public class MyClass
|
||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_a_parameter_is_inside_a_command()
|
public void Analyzer_does_not_report_an_error_if_a_parameter_is_inside_a_command()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -39,32 +39,32 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_a_parameter_is_inside_an_abstract_class()
|
public void Analyzer_does_not_report_an_error_if_a_parameter_is_inside_an_abstract_class()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
public abstract class MyCommand
|
public abstract class MyCommand
|
||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -73,8 +73,7 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,18 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class ParameterMustBeLastIfNonScalarAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeLastIfNonScalarAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class ParameterMustBeLastIfNonScalarAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_a_non_scalar_parameter_is_not_last_in_order()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeLastIfNonScalarAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_a_non_scalar_parameter_is_not_last_in_order()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -26,16 +26,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_a_non_scalar_parameter_is_last_in_order()
|
public void Analyzer_does_not_report_an_error_if_a_non_scalar_parameter_is_last_in_order()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -48,16 +48,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_no_non_scalar_parameters_are_defined()
|
public void Analyzer_does_not_report_an_error_if_no_non_scalar_parameters_are_defined()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -70,16 +70,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -88,8 +88,7 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,18 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class ParameterMustBeSingleIfNonScalarAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeSingleIfNonScalarAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class ParameterMustBeSingleIfNonScalarAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_more_than_one_non_scalar_parameters_are_defined()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeSingleIfNonScalarAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_more_than_one_non_scalar_parameters_are_defined()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -26,16 +26,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_only_one_non_scalar_parameter_is_defined()
|
public void Analyzer_does_not_report_an_error_if_only_one_non_scalar_parameter_is_defined()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -48,16 +48,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_no_non_scalar_parameters_are_defined()
|
public void Analyzer_does_not_report_an_error_if_no_non_scalar_parameters_are_defined()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -70,16 +70,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -88,8 +88,7 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,18 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class ParameterMustHaveUniqueNameAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveUniqueNameAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class ParameterMustHaveUniqueNameAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_a_parameter_has_the_same_name_as_another_parameter()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveUniqueNameAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_a_parameter_has_the_same_name_as_another_parameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -26,16 +26,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_a_parameter_has_unique_name()
|
public void Analyzer_does_not_report_an_error_if_a_parameter_has_unique_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -48,16 +48,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -66,8 +66,7 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,18 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class ParameterMustHaveUniqueOrderAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveUniqueOrderAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class ParameterMustHaveUniqueOrderAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_a_parameter_has_the_same_order_as_another_parameter()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveUniqueOrderAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_a_parameter_has_the_same_order_as_another_parameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -26,16 +26,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_a_parameter_has_unique_order()
|
public void Analyzer_does_not_report_an_error_if_a_parameter_has_unique_order()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -48,16 +48,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -66,8 +66,7 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,18 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class ParameterMustHaveValidConverterAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveValidConverterAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class ParameterMustHaveValidConverterAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_the_specified_parameter_converter_does_not_derive_from_BindingConverter()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveValidConverterAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_the_specified_parameter_converter_does_not_derive_from_BindingConverter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
public class MyConverter
|
public class MyConverter
|
||||||
{
|
{
|
||||||
public string Convert(string rawValue) => rawValue;
|
public string Convert(string rawValue) => rawValue;
|
||||||
@@ -28,16 +28,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_the_specified_parameter_converter_derives_from_BindingConverter()
|
public void Analyzer_does_not_report_an_error_if_the_specified_parameter_converter_derives_from_BindingConverter()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
public class MyConverter : BindingConverter<string>
|
public class MyConverter : BindingConverter<string>
|
||||||
{
|
{
|
||||||
public override string Convert(string rawValue) => rawValue;
|
public override string Convert(string rawValue) => rawValue;
|
||||||
@@ -52,16 +52,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_a_parameter_does_not_have_a_converter()
|
public void Analyzer_does_not_report_an_error_if_a_parameter_does_not_have_a_converter()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -71,16 +71,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -89,8 +89,7 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,18 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class ParameterMustHaveValidValidatorsAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveValidValidatorsAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class ParameterMustHaveValidValidatorsAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_one_of_the_specified_parameter_validators_does_not_derive_from_BindingValidator()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveValidValidatorsAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_one_of_the_specified_parameter_validators_does_not_derive_from_BindingValidator()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
public class MyValidator
|
public class MyValidator
|
||||||
{
|
{
|
||||||
public void Validate(string value) {}
|
public void Validate(string value) {}
|
||||||
@@ -28,16 +28,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_all_specified_parameter_validators_derive_from_BindingValidator()
|
public void Analyzer_does_not_report_an_error_if_all_specified_parameter_validators_derive_from_BindingValidator()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
public class MyValidator : BindingValidator<string>
|
public class MyValidator : BindingValidator<string>
|
||||||
{
|
{
|
||||||
public override BindingValidationError Validate(string value) => Ok();
|
public override BindingValidationError Validate(string value) => Ok();
|
||||||
@@ -52,16 +52,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_a_parameter_does_not_have_validators()
|
public void Analyzer_does_not_report_an_error_if_a_parameter_does_not_have_validators()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -71,16 +71,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -89,8 +89,7 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,18 @@
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests
|
namespace CliFx.Analyzers.Tests;
|
||||||
{
|
|
||||||
public class SystemConsoleShouldBeAvoidedAnalyzerSpecs
|
|
||||||
{
|
|
||||||
private static DiagnosticAnalyzer Analyzer { get; } = new SystemConsoleShouldBeAvoidedAnalyzer();
|
|
||||||
|
|
||||||
[Fact]
|
public class SystemConsoleShouldBeAvoidedAnalyzerSpecs
|
||||||
public void Analyzer_reports_an_error_if_a_command_calls_a_method_on_SystemConsole()
|
{
|
||||||
{
|
private static DiagnosticAnalyzer Analyzer { get; } = new SystemConsoleShouldBeAvoidedAnalyzer();
|
||||||
// Arrange
|
|
||||||
// language=cs
|
[Fact]
|
||||||
const string code = @"
|
public void Analyzer_reports_an_error_if_a_command_calls_a_method_on_SystemConsole()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -24,16 +24,16 @@ public class MyCommand : ICommand
|
|||||||
}
|
}
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_reports_an_error_if_a_command_accesses_a_property_on_SystemConsole()
|
public void Analyzer_reports_an_error_if_a_command_accesses_a_property_on_SystemConsole()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -43,16 +43,16 @@ public class MyCommand : ICommand
|
|||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
}";
|
}";
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_reports_an_error_if_a_command_calls_a_method_on_a_property_of_SystemConsole()
|
public void Analyzer_reports_an_error_if_a_command_calls_a_method_on_a_property_of_SystemConsole()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -63,16 +63,16 @@ public class MyCommand : ICommand
|
|||||||
}
|
}
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().ProduceDiagnostics(code);
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_a_command_interacts_with_the_console_through_IConsole()
|
public void Analyzer_does_not_report_an_error_if_a_command_interacts_with_the_console_through_IConsole()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -83,16 +83,16 @@ public class MyCommand : ICommand
|
|||||||
}
|
}
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_IConsole_is_not_available_in_the_current_method()
|
public void Analyzer_does_not_report_an_error_if_IConsole_is_not_available_in_the_current_method()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -101,16 +101,16 @@ public class MyCommand : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_a_command_does_not_access_SystemConsole()
|
public void Analyzer_does_not_report_an_error_if_a_command_does_not_access_SystemConsole()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
const string code = @"
|
const string code = @"
|
||||||
[Command]
|
[Command]
|
||||||
public class MyCommand : ICommand
|
public class MyCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -120,8 +120,7 @@ public class MyCommand : ICommand
|
|||||||
}
|
}
|
||||||
}";
|
}";
|
||||||
|
|
||||||
// Act & assert
|
// Act & assert
|
||||||
Analyzer.Should().NotProduceDiagnostics(code);
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,158 +11,157 @@ using Microsoft.CodeAnalysis.CSharp;
|
|||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Microsoft.CodeAnalysis.Text;
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Tests.Utils
|
namespace CliFx.Analyzers.Tests.Utils;
|
||||||
{
|
|
||||||
internal class AnalyzerAssertions : ReferenceTypeAssertions<DiagnosticAnalyzer, AnalyzerAssertions>
|
|
||||||
{
|
|
||||||
protected override string Identifier { get; } = "analyzer";
|
|
||||||
|
|
||||||
public AnalyzerAssertions(DiagnosticAnalyzer analyzer)
|
internal class AnalyzerAssertions : ReferenceTypeAssertions<DiagnosticAnalyzer, AnalyzerAssertions>
|
||||||
: base(analyzer)
|
{
|
||||||
|
protected override string Identifier { get; } = "analyzer";
|
||||||
|
|
||||||
|
public AnalyzerAssertions(DiagnosticAnalyzer analyzer)
|
||||||
|
: base(analyzer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private Compilation Compile(string sourceCode)
|
||||||
|
{
|
||||||
|
// Get default system namespaces
|
||||||
|
var defaultSystemNamespaces = new[]
|
||||||
{
|
{
|
||||||
|
"System",
|
||||||
|
"System.Collections.Generic",
|
||||||
|
"System.Threading.Tasks"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get default CliFx namespaces
|
||||||
|
var defaultCliFxNamespaces = typeof(ICommand)
|
||||||
|
.Assembly
|
||||||
|
.GetTypes()
|
||||||
|
.Where(t => t.IsPublic)
|
||||||
|
.Select(t => t.Namespace)
|
||||||
|
.Distinct()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
// Append default imports to the source code
|
||||||
|
var sourceCodeWithUsings =
|
||||||
|
string.Join(Environment.NewLine, defaultSystemNamespaces.Select(n => $"using {n};")) +
|
||||||
|
string.Join(Environment.NewLine, defaultCliFxNamespaces.Select(n => $"using {n};")) +
|
||||||
|
Environment.NewLine +
|
||||||
|
sourceCode;
|
||||||
|
|
||||||
|
// Parse the source code
|
||||||
|
var ast = SyntaxFactory.ParseSyntaxTree(
|
||||||
|
SourceText.From(sourceCodeWithUsings),
|
||||||
|
CSharpParseOptions.Default
|
||||||
|
);
|
||||||
|
|
||||||
|
// Compile the code to IL
|
||||||
|
var compilation = CSharpCompilation.Create(
|
||||||
|
"CliFxTests_DynamicAssembly_" + Guid.NewGuid(),
|
||||||
|
new[] {ast},
|
||||||
|
ReferenceAssemblies.Net50
|
||||||
|
.Append(MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location)),
|
||||||
|
// DLL to avoid having to define the Main() method
|
||||||
|
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
|
||||||
|
);
|
||||||
|
|
||||||
|
var compilationErrors = compilation
|
||||||
|
.GetDiagnostics()
|
||||||
|
.Where(d => d.Severity >= DiagnosticSeverity.Error)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (compilationErrors.Any())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Failed to compile code." +
|
||||||
|
Environment.NewLine +
|
||||||
|
string.Join(Environment.NewLine, compilationErrors.Select(e => e.ToString()))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Compilation Compile(string sourceCode)
|
return compilation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<Diagnostic> GetProducedDiagnostics(string sourceCode)
|
||||||
|
{
|
||||||
|
var analyzers = ImmutableArray.Create(Subject);
|
||||||
|
var compilation = Compile(sourceCode);
|
||||||
|
|
||||||
|
return compilation
|
||||||
|
.WithAnalyzers(analyzers)
|
||||||
|
.GetAnalyzerDiagnosticsAsync(analyzers, default)
|
||||||
|
.GetAwaiter()
|
||||||
|
.GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ProduceDiagnostics(string sourceCode)
|
||||||
|
{
|
||||||
|
var expectedDiagnostics = Subject.SupportedDiagnostics;
|
||||||
|
var producedDiagnostics = GetProducedDiagnostics(sourceCode);
|
||||||
|
|
||||||
|
var expectedDiagnosticIds = expectedDiagnostics.Select(d => d.Id).Distinct().ToArray();
|
||||||
|
var producedDiagnosticIds = producedDiagnostics.Select(d => d.Id).Distinct().ToArray();
|
||||||
|
|
||||||
|
var result =
|
||||||
|
expectedDiagnosticIds.Intersect(producedDiagnosticIds).Count() ==
|
||||||
|
expectedDiagnosticIds.Length;
|
||||||
|
|
||||||
|
Execute.Assertion.ForCondition(result).FailWith(() =>
|
||||||
{
|
{
|
||||||
// Get default system namespaces
|
var buffer = new StringBuilder();
|
||||||
var defaultSystemNamespaces = new[]
|
|
||||||
|
buffer.AppendLine("Expected and produced diagnostics do not match.");
|
||||||
|
buffer.AppendLine();
|
||||||
|
|
||||||
|
buffer.AppendLine("Expected diagnostics:");
|
||||||
|
|
||||||
|
foreach (var expectedDiagnostic in expectedDiagnostics)
|
||||||
{
|
{
|
||||||
"System",
|
buffer.Append(" - ");
|
||||||
"System.Collections.Generic",
|
buffer.Append(expectedDiagnostic.Id);
|
||||||
"System.Threading.Tasks"
|
buffer.AppendLine();
|
||||||
};
|
|
||||||
|
|
||||||
// Get default CliFx namespaces
|
|
||||||
var defaultCliFxNamespaces = typeof(ICommand)
|
|
||||||
.Assembly
|
|
||||||
.GetTypes()
|
|
||||||
.Where(t => t.IsPublic)
|
|
||||||
.Select(t => t.Namespace)
|
|
||||||
.Distinct()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
// Append default imports to the source code
|
|
||||||
var sourceCodeWithUsings =
|
|
||||||
string.Join(Environment.NewLine, defaultSystemNamespaces.Select(n => $"using {n};")) +
|
|
||||||
string.Join(Environment.NewLine, defaultCliFxNamespaces.Select(n => $"using {n};")) +
|
|
||||||
Environment.NewLine +
|
|
||||||
sourceCode;
|
|
||||||
|
|
||||||
// Parse the source code
|
|
||||||
var ast = SyntaxFactory.ParseSyntaxTree(
|
|
||||||
SourceText.From(sourceCodeWithUsings),
|
|
||||||
CSharpParseOptions.Default
|
|
||||||
);
|
|
||||||
|
|
||||||
// Compile the code to IL
|
|
||||||
var compilation = CSharpCompilation.Create(
|
|
||||||
"CliFxTests_DynamicAssembly_" + Guid.NewGuid(),
|
|
||||||
new[] {ast},
|
|
||||||
ReferenceAssemblies.Net50
|
|
||||||
.Append(MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location)),
|
|
||||||
// DLL to avoid having to define the Main() method
|
|
||||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
|
|
||||||
);
|
|
||||||
|
|
||||||
var compilationErrors = compilation
|
|
||||||
.GetDiagnostics()
|
|
||||||
.Where(d => d.Severity >= DiagnosticSeverity.Error)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
if (compilationErrors.Any())
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"Failed to compile code." +
|
|
||||||
Environment.NewLine +
|
|
||||||
string.Join(Environment.NewLine, compilationErrors.Select(e => e.ToString()))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return compilation;
|
buffer.AppendLine();
|
||||||
}
|
|
||||||
|
|
||||||
private IReadOnlyList<Diagnostic> GetProducedDiagnostics(string sourceCode)
|
buffer.AppendLine("Produced diagnostics:");
|
||||||
{
|
|
||||||
var analyzers = ImmutableArray.Create(Subject);
|
|
||||||
var compilation = Compile(sourceCode);
|
|
||||||
|
|
||||||
return compilation
|
foreach (var producedDiagnostic in producedDiagnostics)
|
||||||
.WithAnalyzers(analyzers)
|
|
||||||
.GetAnalyzerDiagnosticsAsync(analyzers, default)
|
|
||||||
.GetAwaiter()
|
|
||||||
.GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ProduceDiagnostics(string sourceCode)
|
|
||||||
{
|
|
||||||
var expectedDiagnostics = Subject.SupportedDiagnostics;
|
|
||||||
var producedDiagnostics = GetProducedDiagnostics(sourceCode);
|
|
||||||
|
|
||||||
var expectedDiagnosticIds = expectedDiagnostics.Select(d => d.Id).Distinct().ToArray();
|
|
||||||
var producedDiagnosticIds = producedDiagnostics.Select(d => d.Id).Distinct().ToArray();
|
|
||||||
|
|
||||||
var result =
|
|
||||||
expectedDiagnosticIds.Intersect(producedDiagnosticIds).Count() ==
|
|
||||||
expectedDiagnosticIds.Length;
|
|
||||||
|
|
||||||
Execute.Assertion.ForCondition(result).FailWith(() =>
|
|
||||||
{
|
{
|
||||||
var buffer = new StringBuilder();
|
buffer.Append(" - ");
|
||||||
|
buffer.Append(producedDiagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
buffer.AppendLine("Expected and produced diagnostics do not match.");
|
return new FailReason(buffer.ToString());
|
||||||
buffer.AppendLine();
|
});
|
||||||
|
|
||||||
buffer.AppendLine("Expected diagnostics:");
|
|
||||||
|
|
||||||
foreach (var expectedDiagnostic in expectedDiagnostics)
|
|
||||||
{
|
|
||||||
buffer.Append(" - ");
|
|
||||||
buffer.Append(expectedDiagnostic.Id);
|
|
||||||
buffer.AppendLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.AppendLine();
|
|
||||||
|
|
||||||
buffer.AppendLine("Produced diagnostics:");
|
|
||||||
|
|
||||||
foreach (var producedDiagnostic in producedDiagnostics)
|
|
||||||
{
|
|
||||||
buffer.Append(" - ");
|
|
||||||
buffer.Append(producedDiagnostic);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new FailReason(buffer.ToString());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void NotProduceDiagnostics(string sourceCode)
|
|
||||||
{
|
|
||||||
var producedDiagnostics = GetProducedDiagnostics(sourceCode);
|
|
||||||
|
|
||||||
var result = !producedDiagnostics.Any();
|
|
||||||
|
|
||||||
Execute.Assertion.ForCondition(result).FailWith(() =>
|
|
||||||
{
|
|
||||||
var buffer = new StringBuilder();
|
|
||||||
|
|
||||||
buffer.AppendLine("Expected no produced diagnostics.");
|
|
||||||
buffer.AppendLine();
|
|
||||||
|
|
||||||
buffer.AppendLine("Produced diagnostics:");
|
|
||||||
|
|
||||||
foreach (var producedDiagnostic in producedDiagnostics)
|
|
||||||
{
|
|
||||||
buffer.Append(" - ");
|
|
||||||
buffer.Append(producedDiagnostic);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new FailReason(buffer.ToString());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class AnalyzerAssertionsExtensions
|
public void NotProduceDiagnostics(string sourceCode)
|
||||||
{
|
{
|
||||||
public static AnalyzerAssertions Should(this DiagnosticAnalyzer analyzer) => new(analyzer);
|
var producedDiagnostics = GetProducedDiagnostics(sourceCode);
|
||||||
|
|
||||||
|
var result = !producedDiagnostics.Any();
|
||||||
|
|
||||||
|
Execute.Assertion.ForCondition(result).FailWith(() =>
|
||||||
|
{
|
||||||
|
var buffer = new StringBuilder();
|
||||||
|
|
||||||
|
buffer.AppendLine("Expected no produced diagnostics.");
|
||||||
|
buffer.AppendLine();
|
||||||
|
|
||||||
|
buffer.AppendLine("Produced diagnostics:");
|
||||||
|
|
||||||
|
foreach (var producedDiagnostic in producedDiagnostics)
|
||||||
|
{
|
||||||
|
buffer.Append(" - ");
|
||||||
|
buffer.Append(producedDiagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FailReason(buffer.ToString());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class AnalyzerAssertionsExtensions
|
||||||
|
{
|
||||||
|
public static AnalyzerAssertions Should(this DiagnosticAnalyzer analyzer) => new(analyzer);
|
||||||
}
|
}
|
||||||
@@ -3,38 +3,37 @@ using CliFx.Analyzers.Utils.Extensions;
|
|||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
public abstract class AnalyzerBase : DiagnosticAnalyzer
|
||||||
{
|
{
|
||||||
public abstract class AnalyzerBase : DiagnosticAnalyzer
|
public DiagnosticDescriptor SupportedDiagnostic { get; }
|
||||||
|
|
||||||
|
public sealed override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
|
||||||
|
|
||||||
|
protected AnalyzerBase(
|
||||||
|
string diagnosticTitle,
|
||||||
|
string diagnosticMessage,
|
||||||
|
DiagnosticSeverity diagnosticSeverity = DiagnosticSeverity.Error)
|
||||||
{
|
{
|
||||||
public DiagnosticDescriptor SupportedDiagnostic { get; }
|
SupportedDiagnostic = new DiagnosticDescriptor(
|
||||||
|
"CliFx_" + GetType().Name.TrimEnd("Analyzer"),
|
||||||
|
diagnosticTitle,
|
||||||
|
diagnosticMessage,
|
||||||
|
"CliFx",
|
||||||
|
diagnosticSeverity,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
public sealed override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
|
SupportedDiagnostics = ImmutableArray.Create(SupportedDiagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
protected AnalyzerBase(
|
protected Diagnostic CreateDiagnostic(Location location) =>
|
||||||
string diagnosticTitle,
|
Diagnostic.Create(SupportedDiagnostic, location);
|
||||||
string diagnosticMessage,
|
|
||||||
DiagnosticSeverity diagnosticSeverity = DiagnosticSeverity.Error)
|
|
||||||
{
|
|
||||||
SupportedDiagnostic = new DiagnosticDescriptor(
|
|
||||||
"CliFx_" + GetType().Name.TrimEnd("Analyzer"),
|
|
||||||
diagnosticTitle,
|
|
||||||
diagnosticMessage,
|
|
||||||
"CliFx",
|
|
||||||
diagnosticSeverity,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
SupportedDiagnostics = ImmutableArray.Create(SupportedDiagnostic);
|
public override void Initialize(AnalysisContext context)
|
||||||
}
|
{
|
||||||
|
context.EnableConcurrentExecution();
|
||||||
protected Diagnostic CreateDiagnostic(Location location) =>
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||||
Diagnostic.Create(SupportedDiagnostic, location);
|
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
|
||||||
{
|
|
||||||
context.EnableConcurrentExecution();
|
|
||||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,50 +5,49 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class CommandMustBeAnnotatedAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public CommandMustBeAnnotatedAnalyzer()
|
||||||
public class CommandMustBeAnnotatedAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
$"Commands must be annotated with `{SymbolNames.CliFxCommandAttribute}`",
|
||||||
|
$"This type must be annotated with `{SymbolNames.CliFxCommandAttribute}` in order to be a valid command.")
|
||||||
{
|
{
|
||||||
public CommandMustBeAnnotatedAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
$"Commands must be annotated with `{SymbolNames.CliFxCommandAttribute}`",
|
private void Analyze(
|
||||||
$"This type must be annotated with `{SymbolNames.CliFxCommandAttribute}` in order to be a valid command.")
|
SyntaxNodeAnalysisContext context,
|
||||||
|
ClassDeclarationSyntax classDeclaration,
|
||||||
|
ITypeSymbol type)
|
||||||
|
{
|
||||||
|
// Ignore abstract classes, because they may be used to define
|
||||||
|
// base implementations for commands, in which case the command
|
||||||
|
// attribute doesn't make sense.
|
||||||
|
if (type.IsAbstract)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var implementsCommandInterface = type
|
||||||
|
.AllInterfaces
|
||||||
|
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface));
|
||||||
|
|
||||||
|
var hasCommandAttribute = type
|
||||||
|
.GetAttributes()
|
||||||
|
.Select(a => a.AttributeClass)
|
||||||
|
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandAttribute));
|
||||||
|
|
||||||
|
// If the interface is implemented, but the attribute is missing,
|
||||||
|
// then it's very likely a user error.
|
||||||
|
if (implementsCommandInterface && !hasCommandAttribute)
|
||||||
{
|
{
|
||||||
}
|
context.ReportDiagnostic(CreateDiagnostic(classDeclaration.GetLocation()));
|
||||||
|
|
||||||
private void Analyze(
|
|
||||||
SyntaxNodeAnalysisContext context,
|
|
||||||
ClassDeclarationSyntax classDeclaration,
|
|
||||||
ITypeSymbol type)
|
|
||||||
{
|
|
||||||
// Ignore abstract classes, because they may be used to define
|
|
||||||
// base implementations for commands, in which case the command
|
|
||||||
// attribute doesn't make sense.
|
|
||||||
if (type.IsAbstract)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var implementsCommandInterface = type
|
|
||||||
.AllInterfaces
|
|
||||||
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface));
|
|
||||||
|
|
||||||
var hasCommandAttribute = type
|
|
||||||
.GetAttributes()
|
|
||||||
.Select(a => a.AttributeClass)
|
|
||||||
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandAttribute));
|
|
||||||
|
|
||||||
// If the interface is implemented, but the attribute is missing,
|
|
||||||
// then it's very likely a user error.
|
|
||||||
if (implementsCommandInterface && !hasCommandAttribute)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(classDeclaration.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
|
||||||
{
|
|
||||||
base.Initialize(context);
|
|
||||||
context.HandleClassDeclaration(Analyze);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
base.Initialize(context);
|
||||||
|
context.HandleClassDeclaration(Analyze);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,44 +5,43 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class CommandMustImplementInterfaceAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public CommandMustImplementInterfaceAnalyzer()
|
||||||
public class CommandMustImplementInterfaceAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
$"Commands must implement `{SymbolNames.CliFxCommandInterface}` interface",
|
||||||
|
$"This type must implement `{SymbolNames.CliFxCommandInterface}` interface in order to be a valid command.")
|
||||||
{
|
{
|
||||||
public CommandMustImplementInterfaceAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
$"Commands must implement `{SymbolNames.CliFxCommandInterface}` interface",
|
private void Analyze(
|
||||||
$"This type must implement `{SymbolNames.CliFxCommandInterface}` interface in order to be a valid command.")
|
SyntaxNodeAnalysisContext context,
|
||||||
|
ClassDeclarationSyntax classDeclaration,
|
||||||
|
ITypeSymbol type)
|
||||||
|
{
|
||||||
|
var hasCommandAttribute = type
|
||||||
|
.GetAttributes()
|
||||||
|
.Select(a => a.AttributeClass)
|
||||||
|
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandAttribute));
|
||||||
|
|
||||||
|
var implementsCommandInterface = type
|
||||||
|
.AllInterfaces
|
||||||
|
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface));
|
||||||
|
|
||||||
|
// If the attribute is present, but the interface is not implemented,
|
||||||
|
// it's very likely a user error.
|
||||||
|
if (hasCommandAttribute && !implementsCommandInterface)
|
||||||
{
|
{
|
||||||
}
|
context.ReportDiagnostic(CreateDiagnostic(classDeclaration.GetLocation()));
|
||||||
|
|
||||||
private void Analyze(
|
|
||||||
SyntaxNodeAnalysisContext context,
|
|
||||||
ClassDeclarationSyntax classDeclaration,
|
|
||||||
ITypeSymbol type)
|
|
||||||
{
|
|
||||||
var hasCommandAttribute = type
|
|
||||||
.GetAttributes()
|
|
||||||
.Select(a => a.AttributeClass)
|
|
||||||
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandAttribute));
|
|
||||||
|
|
||||||
var implementsCommandInterface = type
|
|
||||||
.AllInterfaces
|
|
||||||
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface));
|
|
||||||
|
|
||||||
// If the attribute is present, but the interface is not implemented,
|
|
||||||
// it's very likely a user error.
|
|
||||||
if (hasCommandAttribute && !implementsCommandInterface)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(classDeclaration.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
|
||||||
{
|
|
||||||
base.Initialize(context);
|
|
||||||
context.HandleClassDeclaration(Analyze);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
base.Initialize(context);
|
||||||
|
context.HandleClassDeclaration(Analyze);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,81 +3,80 @@ using Microsoft.CodeAnalysis;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CliFx.Analyzers.Utils.Extensions;
|
using CliFx.Analyzers.Utils.Extensions;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.ObjectModel
|
namespace CliFx.Analyzers.ObjectModel;
|
||||||
|
|
||||||
|
internal partial class CommandOptionSymbol
|
||||||
{
|
{
|
||||||
internal partial class CommandOptionSymbol
|
public string? Name { get; }
|
||||||
|
|
||||||
|
public char? ShortName { get; }
|
||||||
|
|
||||||
|
public ITypeSymbol? ConverterType { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<ITypeSymbol> ValidatorTypes { get; }
|
||||||
|
|
||||||
|
public CommandOptionSymbol(
|
||||||
|
string? name,
|
||||||
|
char? shortName,
|
||||||
|
ITypeSymbol? converterType,
|
||||||
|
IReadOnlyList<ITypeSymbol> validatorTypes)
|
||||||
{
|
{
|
||||||
public string? Name { get; }
|
Name = name;
|
||||||
|
ShortName = shortName;
|
||||||
|
ConverterType = converterType;
|
||||||
|
ValidatorTypes = validatorTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public char? ShortName { get; }
|
internal partial class CommandOptionSymbol
|
||||||
|
{
|
||||||
|
private static AttributeData? TryGetOptionAttribute(IPropertySymbol property) =>
|
||||||
|
property
|
||||||
|
.GetAttributes()
|
||||||
|
.FirstOrDefault(a => a.AttributeClass.DisplayNameMatches(SymbolNames.CliFxCommandOptionAttribute));
|
||||||
|
|
||||||
public ITypeSymbol? ConverterType { get; }
|
private static CommandOptionSymbol FromAttribute(AttributeData attribute)
|
||||||
|
{
|
||||||
|
var name = attribute
|
||||||
|
.ConstructorArguments
|
||||||
|
.Where(a => a.Type.DisplayNameMatches("string") || a.Type.DisplayNameMatches("System.String"))
|
||||||
|
.Select(a => a.Value)
|
||||||
|
.FirstOrDefault() as string;
|
||||||
|
|
||||||
public IReadOnlyList<ITypeSymbol> ValidatorTypes { get; }
|
var shortName = attribute
|
||||||
|
.ConstructorArguments
|
||||||
|
.Where(a => a.Type.DisplayNameMatches("char") || a.Type.DisplayNameMatches("System.Char"))
|
||||||
|
.Select(a => a.Value)
|
||||||
|
.FirstOrDefault() as char?;
|
||||||
|
|
||||||
public CommandOptionSymbol(
|
var converter = attribute
|
||||||
string? name,
|
.NamedArguments
|
||||||
char? shortName,
|
.Where(a => a.Key == "Converter")
|
||||||
ITypeSymbol? converterType,
|
.Select(a => a.Value.Value)
|
||||||
IReadOnlyList<ITypeSymbol> validatorTypes)
|
.Cast<ITypeSymbol?>()
|
||||||
{
|
.FirstOrDefault();
|
||||||
Name = name;
|
|
||||||
ShortName = shortName;
|
var validators = attribute
|
||||||
ConverterType = converterType;
|
.NamedArguments
|
||||||
ValidatorTypes = validatorTypes;
|
.Where(a => a.Key == "Validators")
|
||||||
}
|
.SelectMany(a => a.Value.Values)
|
||||||
|
.Select(c => c.Value)
|
||||||
|
.Cast<ITypeSymbol>()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return new CommandOptionSymbol(name, shortName, converter, validators);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class CommandOptionSymbol
|
public static CommandOptionSymbol? TryResolve(IPropertySymbol property)
|
||||||
{
|
{
|
||||||
private static AttributeData? TryGetOptionAttribute(IPropertySymbol property) =>
|
var attribute = TryGetOptionAttribute(property);
|
||||||
property
|
|
||||||
.GetAttributes()
|
|
||||||
.FirstOrDefault(a => a.AttributeClass.DisplayNameMatches(SymbolNames.CliFxCommandOptionAttribute));
|
|
||||||
|
|
||||||
private static CommandOptionSymbol FromAttribute(AttributeData attribute)
|
if (attribute is null)
|
||||||
{
|
return null;
|
||||||
var name = attribute
|
|
||||||
.ConstructorArguments
|
|
||||||
.Where(a => a.Type.DisplayNameMatches("string") || a.Type.DisplayNameMatches("System.String"))
|
|
||||||
.Select(a => a.Value)
|
|
||||||
.FirstOrDefault() as string;
|
|
||||||
|
|
||||||
var shortName = attribute
|
return FromAttribute(attribute);
|
||||||
.ConstructorArguments
|
|
||||||
.Where(a => a.Type.DisplayNameMatches("char") || a.Type.DisplayNameMatches("System.Char"))
|
|
||||||
.Select(a => a.Value)
|
|
||||||
.FirstOrDefault() as char?;
|
|
||||||
|
|
||||||
var converter = attribute
|
|
||||||
.NamedArguments
|
|
||||||
.Where(a => a.Key == "Converter")
|
|
||||||
.Select(a => a.Value.Value)
|
|
||||||
.Cast<ITypeSymbol?>()
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
var validators = attribute
|
|
||||||
.NamedArguments
|
|
||||||
.Where(a => a.Key == "Validators")
|
|
||||||
.SelectMany(a => a.Value.Values)
|
|
||||||
.Select(c => c.Value)
|
|
||||||
.Cast<ITypeSymbol>()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
return new CommandOptionSymbol(name, shortName, converter, validators);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CommandOptionSymbol? TryResolve(IPropertySymbol property)
|
|
||||||
{
|
|
||||||
var attribute = TryGetOptionAttribute(property);
|
|
||||||
|
|
||||||
if (attribute is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return FromAttribute(attribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsOptionProperty(IPropertySymbol property) =>
|
|
||||||
TryGetOptionAttribute(property) is not null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsOptionProperty(IPropertySymbol property) =>
|
||||||
|
TryGetOptionAttribute(property) is not null;
|
||||||
}
|
}
|
||||||
@@ -3,80 +3,79 @@ using System.Linq;
|
|||||||
using CliFx.Analyzers.Utils.Extensions;
|
using CliFx.Analyzers.Utils.Extensions;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.ObjectModel
|
namespace CliFx.Analyzers.ObjectModel;
|
||||||
|
|
||||||
|
internal partial class CommandParameterSymbol
|
||||||
{
|
{
|
||||||
internal partial class CommandParameterSymbol
|
public int Order { get; }
|
||||||
|
|
||||||
|
public string? Name { get; }
|
||||||
|
|
||||||
|
public ITypeSymbol? ConverterType { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<ITypeSymbol> ValidatorTypes { get; }
|
||||||
|
|
||||||
|
public CommandParameterSymbol(
|
||||||
|
int order,
|
||||||
|
string? name,
|
||||||
|
ITypeSymbol? converterType,
|
||||||
|
IReadOnlyList<ITypeSymbol> validatorTypes)
|
||||||
{
|
{
|
||||||
public int Order { get; }
|
Order = order;
|
||||||
|
Name = name;
|
||||||
|
ConverterType = converterType;
|
||||||
|
ValidatorTypes = validatorTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string? Name { get; }
|
internal partial class CommandParameterSymbol
|
||||||
|
{
|
||||||
|
private static AttributeData? TryGetParameterAttribute(IPropertySymbol property) =>
|
||||||
|
property
|
||||||
|
.GetAttributes()
|
||||||
|
.FirstOrDefault(a => a.AttributeClass.DisplayNameMatches(SymbolNames.CliFxCommandParameterAttribute));
|
||||||
|
|
||||||
public ITypeSymbol? ConverterType { get; }
|
private static CommandParameterSymbol FromAttribute(AttributeData attribute)
|
||||||
|
{
|
||||||
|
var order = (int) attribute
|
||||||
|
.ConstructorArguments
|
||||||
|
.Select(a => a.Value)
|
||||||
|
.First()!;
|
||||||
|
|
||||||
public IReadOnlyList<ITypeSymbol> ValidatorTypes { get; }
|
var name = attribute
|
||||||
|
.NamedArguments
|
||||||
|
.Where(a => a.Key == "Name")
|
||||||
|
.Select(a => a.Value.Value)
|
||||||
|
.FirstOrDefault() as string;
|
||||||
|
|
||||||
public CommandParameterSymbol(
|
var converter = attribute
|
||||||
int order,
|
.NamedArguments
|
||||||
string? name,
|
.Where(a => a.Key == "Converter")
|
||||||
ITypeSymbol? converterType,
|
.Select(a => a.Value.Value)
|
||||||
IReadOnlyList<ITypeSymbol> validatorTypes)
|
.Cast<ITypeSymbol?>()
|
||||||
{
|
.FirstOrDefault();
|
||||||
Order = order;
|
|
||||||
Name = name;
|
var validators = attribute
|
||||||
ConverterType = converterType;
|
.NamedArguments
|
||||||
ValidatorTypes = validatorTypes;
|
.Where(a => a.Key == "Validators")
|
||||||
}
|
.SelectMany(a => a.Value.Values)
|
||||||
|
.Select(c => c.Value)
|
||||||
|
.Cast<ITypeSymbol>()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return new CommandParameterSymbol(order, name, converter, validators);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class CommandParameterSymbol
|
public static CommandParameterSymbol? TryResolve(IPropertySymbol property)
|
||||||
{
|
{
|
||||||
private static AttributeData? TryGetParameterAttribute(IPropertySymbol property) =>
|
var attribute = TryGetParameterAttribute(property);
|
||||||
property
|
|
||||||
.GetAttributes()
|
|
||||||
.FirstOrDefault(a => a.AttributeClass.DisplayNameMatches(SymbolNames.CliFxCommandParameterAttribute));
|
|
||||||
|
|
||||||
private static CommandParameterSymbol FromAttribute(AttributeData attribute)
|
if (attribute is null)
|
||||||
{
|
return null;
|
||||||
var order = (int) attribute
|
|
||||||
.ConstructorArguments
|
|
||||||
.Select(a => a.Value)
|
|
||||||
.First()!;
|
|
||||||
|
|
||||||
var name = attribute
|
return FromAttribute(attribute);
|
||||||
.NamedArguments
|
|
||||||
.Where(a => a.Key == "Name")
|
|
||||||
.Select(a => a.Value.Value)
|
|
||||||
.FirstOrDefault() as string;
|
|
||||||
|
|
||||||
var converter = attribute
|
|
||||||
.NamedArguments
|
|
||||||
.Where(a => a.Key == "Converter")
|
|
||||||
.Select(a => a.Value.Value)
|
|
||||||
.Cast<ITypeSymbol?>()
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
var validators = attribute
|
|
||||||
.NamedArguments
|
|
||||||
.Where(a => a.Key == "Validators")
|
|
||||||
.SelectMany(a => a.Value.Values)
|
|
||||||
.Select(c => c.Value)
|
|
||||||
.Cast<ITypeSymbol>()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
return new CommandParameterSymbol(order, name, converter, validators);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CommandParameterSymbol? TryResolve(IPropertySymbol property)
|
|
||||||
{
|
|
||||||
var attribute = TryGetParameterAttribute(property);
|
|
||||||
|
|
||||||
if (attribute is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return FromAttribute(attribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsParameterProperty(IPropertySymbol property) =>
|
|
||||||
TryGetParameterAttribute(property) is not null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsParameterProperty(IPropertySymbol property) =>
|
||||||
|
TryGetParameterAttribute(property) is not null;
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
namespace CliFx.Analyzers.ObjectModel
|
namespace CliFx.Analyzers.ObjectModel;
|
||||||
|
|
||||||
|
internal static class SymbolNames
|
||||||
{
|
{
|
||||||
internal static class SymbolNames
|
public const string CliFxCommandInterface = "CliFx.ICommand";
|
||||||
{
|
public const string CliFxCommandAttribute = "CliFx.Attributes.CommandAttribute";
|
||||||
public const string CliFxCommandInterface = "CliFx.ICommand";
|
public const string CliFxCommandParameterAttribute = "CliFx.Attributes.CommandParameterAttribute";
|
||||||
public const string CliFxCommandAttribute = "CliFx.Attributes.CommandAttribute";
|
public const string CliFxCommandOptionAttribute = "CliFx.Attributes.CommandOptionAttribute";
|
||||||
public const string CliFxCommandParameterAttribute = "CliFx.Attributes.CommandParameterAttribute";
|
public const string CliFxConsoleInterface = "CliFx.Infrastructure.IConsole";
|
||||||
public const string CliFxCommandOptionAttribute = "CliFx.Attributes.CommandOptionAttribute";
|
public const string CliFxBindingConverterInterface = "CliFx.Extensibility.IBindingConverter";
|
||||||
public const string CliFxConsoleInterface = "CliFx.Infrastructure.IConsole";
|
public const string CliFxBindingConverterClass = "CliFx.Extensibility.BindingConverter<T>";
|
||||||
public const string CliFxBindingConverterInterface = "CliFx.Extensibility.IBindingConverter";
|
public const string CliFxBindingValidatorInterface = "CliFx.Extensibility.IBindingValidator";
|
||||||
public const string CliFxBindingConverterClass = "CliFx.Extensibility.BindingConverter<T>";
|
public const string CliFxBindingValidatorClass = "CliFx.Extensibility.BindingValidator<T>";
|
||||||
public const string CliFxBindingValidatorInterface = "CliFx.Extensibility.IBindingValidator";
|
|
||||||
public const string CliFxBindingValidatorClass = "CliFx.Extensibility.BindingValidator<T>";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -5,47 +5,46 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class OptionMustBeInsideCommandAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public OptionMustBeInsideCommandAnalyzer()
|
||||||
public class OptionMustBeInsideCommandAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
"Options must be defined inside commands",
|
||||||
|
$"This option must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`.")
|
||||||
{
|
{
|
||||||
public OptionMustBeInsideCommandAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
"Options must be defined inside commands",
|
private void Analyze(
|
||||||
$"This option must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`.")
|
SyntaxNodeAnalysisContext context,
|
||||||
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
|
IPropertySymbol property)
|
||||||
|
{
|
||||||
|
if (property.ContainingType is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (property.ContainingType.IsAbstract)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!CommandOptionSymbol.IsOptionProperty(property))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var isInsideCommand = property
|
||||||
|
.ContainingType
|
||||||
|
.AllInterfaces
|
||||||
|
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface));
|
||||||
|
|
||||||
|
if (!isInsideCommand)
|
||||||
{
|
{
|
||||||
}
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
|
|
||||||
private void Analyze(
|
|
||||||
SyntaxNodeAnalysisContext context,
|
|
||||||
PropertyDeclarationSyntax propertyDeclaration,
|
|
||||||
IPropertySymbol property)
|
|
||||||
{
|
|
||||||
if (property.ContainingType is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (property.ContainingType.IsAbstract)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!CommandOptionSymbol.IsOptionProperty(property))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var isInsideCommand = property
|
|
||||||
.ContainingType
|
|
||||||
.AllInterfaces
|
|
||||||
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface));
|
|
||||||
|
|
||||||
if (!isInsideCommand)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
|
||||||
{
|
|
||||||
base.Initialize(context);
|
|
||||||
context.HandlePropertyDeclaration(Analyze);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
base.Initialize(context);
|
||||||
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,37 +4,36 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class OptionMustHaveNameOrShortNameAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public OptionMustHaveNameOrShortNameAnalyzer()
|
||||||
public class OptionMustHaveNameOrShortNameAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
"Options must have either a name or short name specified",
|
||||||
|
"This option must have either a name or short name specified.")
|
||||||
{
|
{
|
||||||
public OptionMustHaveNameOrShortNameAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
"Options must have either a name or short name specified",
|
|
||||||
"This option must have either a name or short name specified.")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Analyze(
|
private void Analyze(
|
||||||
SyntaxNodeAnalysisContext context,
|
SyntaxNodeAnalysisContext context,
|
||||||
PropertyDeclarationSyntax propertyDeclaration,
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
IPropertySymbol property)
|
IPropertySymbol property)
|
||||||
{
|
{
|
||||||
var option = CommandOptionSymbol.TryResolve(property);
|
var option = CommandOptionSymbol.TryResolve(property);
|
||||||
if (option is null)
|
if (option is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(option.Name) && option.ShortName is null)
|
if (string.IsNullOrWhiteSpace(option.Name) && option.ShortName is null)
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
|
||||||
{
|
{
|
||||||
base.Initialize(context);
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
context.HandlePropertyDeclaration(Analyze);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
base.Initialize(context);
|
||||||
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,60 +6,59 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class OptionMustHaveUniqueNameAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public OptionMustHaveUniqueNameAnalyzer()
|
||||||
public class OptionMustHaveUniqueNameAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
"Options must have unique names",
|
||||||
|
"This option's name must be unique within the command (comparison IS NOT case sensitive).")
|
||||||
{
|
{
|
||||||
public OptionMustHaveUniqueNameAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
"Options must have unique names",
|
private void Analyze(
|
||||||
"This option's name must be unique within the command (comparison IS NOT case sensitive).")
|
SyntaxNodeAnalysisContext context,
|
||||||
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
|
IPropertySymbol property)
|
||||||
|
{
|
||||||
|
if (property.ContainingType is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var option = CommandOptionSymbol.TryResolve(property);
|
||||||
|
if (option is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(option.Name))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var otherProperties = property
|
||||||
|
.ContainingType
|
||||||
|
.GetMembers()
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.Where(m => !m.Equals(property, SymbolEqualityComparer.Default))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var otherProperty in otherProperties)
|
||||||
{
|
{
|
||||||
}
|
var otherOption = CommandOptionSymbol.TryResolve(otherProperty);
|
||||||
|
if (otherOption is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
private void Analyze(
|
if (string.IsNullOrWhiteSpace(otherOption.Name))
|
||||||
SyntaxNodeAnalysisContext context,
|
continue;
|
||||||
PropertyDeclarationSyntax propertyDeclaration,
|
|
||||||
IPropertySymbol property)
|
|
||||||
{
|
|
||||||
if (property.ContainingType is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var option = CommandOptionSymbol.TryResolve(property);
|
if (string.Equals(option.Name, otherOption.Name, StringComparison.OrdinalIgnoreCase))
|
||||||
if (option is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(option.Name))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var otherProperties = property
|
|
||||||
.ContainingType
|
|
||||||
.GetMembers()
|
|
||||||
.OfType<IPropertySymbol>()
|
|
||||||
.Where(m => !m.Equals(property, SymbolEqualityComparer.Default))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var otherProperty in otherProperties)
|
|
||||||
{
|
{
|
||||||
var otherOption = CommandOptionSymbol.TryResolve(otherProperty);
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
if (otherOption is null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(otherOption.Name))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (string.Equals(option.Name, otherOption.Name, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
public override void Initialize(AnalysisContext context)
|
||||||
{
|
{
|
||||||
base.Initialize(context);
|
base.Initialize(context);
|
||||||
context.HandlePropertyDeclaration(Analyze);
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,60 +5,59 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class OptionMustHaveUniqueShortNameAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public OptionMustHaveUniqueShortNameAnalyzer()
|
||||||
public class OptionMustHaveUniqueShortNameAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
"Options must have unique short names",
|
||||||
|
"This option's short name must be unique within the command (comparison IS case sensitive).")
|
||||||
{
|
{
|
||||||
public OptionMustHaveUniqueShortNameAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
"Options must have unique short names",
|
private void Analyze(
|
||||||
"This option's short name must be unique within the command (comparison IS case sensitive).")
|
SyntaxNodeAnalysisContext context,
|
||||||
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
|
IPropertySymbol property)
|
||||||
|
{
|
||||||
|
if (property.ContainingType is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var option = CommandOptionSymbol.TryResolve(property);
|
||||||
|
if (option is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (option.ShortName is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var otherProperties = property
|
||||||
|
.ContainingType
|
||||||
|
.GetMembers()
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.Where(m => !m.Equals(property, SymbolEqualityComparer.Default))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var otherProperty in otherProperties)
|
||||||
{
|
{
|
||||||
}
|
var otherOption = CommandOptionSymbol.TryResolve(otherProperty);
|
||||||
|
if (otherOption is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
private void Analyze(
|
if (otherOption.ShortName is null)
|
||||||
SyntaxNodeAnalysisContext context,
|
continue;
|
||||||
PropertyDeclarationSyntax propertyDeclaration,
|
|
||||||
IPropertySymbol property)
|
|
||||||
{
|
|
||||||
if (property.ContainingType is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var option = CommandOptionSymbol.TryResolve(property);
|
if (option.ShortName == otherOption.ShortName)
|
||||||
if (option is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (option.ShortName is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var otherProperties = property
|
|
||||||
.ContainingType
|
|
||||||
.GetMembers()
|
|
||||||
.OfType<IPropertySymbol>()
|
|
||||||
.Where(m => !m.Equals(property, SymbolEqualityComparer.Default))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var otherProperty in otherProperties)
|
|
||||||
{
|
{
|
||||||
var otherOption = CommandOptionSymbol.TryResolve(otherProperty);
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
if (otherOption is null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (otherOption.ShortName is null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (option.ShortName == otherOption.ShortName)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
public override void Initialize(AnalysisContext context)
|
||||||
{
|
{
|
||||||
base.Initialize(context);
|
base.Initialize(context);
|
||||||
context.HandlePropertyDeclaration(Analyze);
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,46 +5,45 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class OptionMustHaveValidConverterAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public OptionMustHaveValidConverterAnalyzer()
|
||||||
public class OptionMustHaveValidConverterAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
$"Option converters must derive from `{SymbolNames.CliFxBindingConverterClass}`",
|
||||||
|
$"Converter specified for this option must derive from `{SymbolNames.CliFxBindingConverterClass}`.")
|
||||||
{
|
{
|
||||||
public OptionMustHaveValidConverterAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
$"Option converters must derive from `{SymbolNames.CliFxBindingConverterClass}`",
|
private void Analyze(
|
||||||
$"Converter specified for this option must derive from `{SymbolNames.CliFxBindingConverterClass}`.")
|
SyntaxNodeAnalysisContext context,
|
||||||
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
|
IPropertySymbol property)
|
||||||
|
{
|
||||||
|
var option = CommandOptionSymbol.TryResolve(property);
|
||||||
|
if (option is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (option.ConverterType is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// We check against an internal interface because checking against a generic class is a pain
|
||||||
|
var converterImplementsInterface = option
|
||||||
|
.ConverterType
|
||||||
|
.AllInterfaces
|
||||||
|
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingConverterInterface));
|
||||||
|
|
||||||
|
if (!converterImplementsInterface)
|
||||||
{
|
{
|
||||||
}
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
|
|
||||||
private void Analyze(
|
|
||||||
SyntaxNodeAnalysisContext context,
|
|
||||||
PropertyDeclarationSyntax propertyDeclaration,
|
|
||||||
IPropertySymbol property)
|
|
||||||
{
|
|
||||||
var option = CommandOptionSymbol.TryResolve(property);
|
|
||||||
if (option is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (option.ConverterType is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// We check against an internal interface because checking against a generic class is a pain
|
|
||||||
var converterImplementsInterface = option
|
|
||||||
.ConverterType
|
|
||||||
.AllInterfaces
|
|
||||||
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingConverterInterface));
|
|
||||||
|
|
||||||
if (!converterImplementsInterface)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
|
||||||
{
|
|
||||||
base.Initialize(context);
|
|
||||||
context.HandlePropertyDeclaration(Analyze);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
base.Initialize(context);
|
||||||
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,40 +4,39 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class OptionMustHaveValidNameAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public OptionMustHaveValidNameAnalyzer()
|
||||||
public class OptionMustHaveValidNameAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
"Options must have valid names",
|
||||||
|
"This option's name must be at least 2 characters long and must start with a letter.")
|
||||||
{
|
{
|
||||||
public OptionMustHaveValidNameAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
"Options must have valid names",
|
private void Analyze(
|
||||||
"This option's name must be at least 2 characters long and must start with a letter.")
|
SyntaxNodeAnalysisContext context,
|
||||||
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
|
IPropertySymbol property)
|
||||||
|
{
|
||||||
|
var option = CommandOptionSymbol.TryResolve(property);
|
||||||
|
if (option is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(option.Name))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (option.Name.Length < 2 || !char.IsLetter(option.Name[0]))
|
||||||
{
|
{
|
||||||
}
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
|
|
||||||
private void Analyze(
|
|
||||||
SyntaxNodeAnalysisContext context,
|
|
||||||
PropertyDeclarationSyntax propertyDeclaration,
|
|
||||||
IPropertySymbol property)
|
|
||||||
{
|
|
||||||
var option = CommandOptionSymbol.TryResolve(property);
|
|
||||||
if (option is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(option.Name))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (option.Name.Length < 2 || !char.IsLetter(option.Name[0]))
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
|
||||||
{
|
|
||||||
base.Initialize(context);
|
|
||||||
context.HandlePropertyDeclaration(Analyze);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
base.Initialize(context);
|
||||||
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,40 +4,39 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class OptionMustHaveValidShortNameAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public OptionMustHaveValidShortNameAnalyzer()
|
||||||
public class OptionMustHaveValidShortNameAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
"Option short names must be letter characters",
|
||||||
|
"This option's short name must be a single letter character.")
|
||||||
{
|
{
|
||||||
public OptionMustHaveValidShortNameAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
"Option short names must be letter characters",
|
private void Analyze(
|
||||||
"This option's short name must be a single letter character.")
|
SyntaxNodeAnalysisContext context,
|
||||||
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
|
IPropertySymbol property)
|
||||||
|
{
|
||||||
|
var option = CommandOptionSymbol.TryResolve(property);
|
||||||
|
if (option is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (option.ShortName is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!char.IsLetter(option.ShortName.Value))
|
||||||
{
|
{
|
||||||
}
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
|
|
||||||
private void Analyze(
|
|
||||||
SyntaxNodeAnalysisContext context,
|
|
||||||
PropertyDeclarationSyntax propertyDeclaration,
|
|
||||||
IPropertySymbol property)
|
|
||||||
{
|
|
||||||
var option = CommandOptionSymbol.TryResolve(property);
|
|
||||||
if (option is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (option.ShortName is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!char.IsLetter(option.ShortName.Value))
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
|
||||||
{
|
|
||||||
base.Initialize(context);
|
|
||||||
context.HandlePropertyDeclaration(Analyze);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
base.Initialize(context);
|
||||||
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,48 +5,47 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class OptionMustHaveValidValidatorsAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public OptionMustHaveValidValidatorsAnalyzer()
|
||||||
public class OptionMustHaveValidValidatorsAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
$"Option validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`",
|
||||||
|
$"All validators specified for this option must derive from `{SymbolNames.CliFxBindingValidatorClass}`.")
|
||||||
{
|
{
|
||||||
public OptionMustHaveValidValidatorsAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
$"Option validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`",
|
|
||||||
$"All validators specified for this option must derive from `{SymbolNames.CliFxBindingValidatorClass}`.")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Analyze(
|
private void Analyze(
|
||||||
SyntaxNodeAnalysisContext context,
|
SyntaxNodeAnalysisContext context,
|
||||||
PropertyDeclarationSyntax propertyDeclaration,
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
IPropertySymbol property)
|
IPropertySymbol property)
|
||||||
{
|
{
|
||||||
var option = CommandOptionSymbol.TryResolve(property);
|
var option = CommandOptionSymbol.TryResolve(property);
|
||||||
if (option is null)
|
if (option is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var validatorType in option.ValidatorTypes)
|
foreach (var validatorType in option.ValidatorTypes)
|
||||||
|
{
|
||||||
|
// We check against an internal interface because checking against a generic class is a pain
|
||||||
|
var validatorImplementsInterface = validatorType
|
||||||
|
.AllInterfaces
|
||||||
|
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingValidatorInterface));
|
||||||
|
|
||||||
|
if (!validatorImplementsInterface)
|
||||||
{
|
{
|
||||||
// We check against an internal interface because checking against a generic class is a pain
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
var validatorImplementsInterface = validatorType
|
|
||||||
.AllInterfaces
|
|
||||||
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingValidatorInterface));
|
|
||||||
|
|
||||||
if (!validatorImplementsInterface)
|
// No need to report multiple identical diagnostics on the same node
|
||||||
{
|
break;
|
||||||
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
|
||||||
|
|
||||||
// No need to report multiple identical diagnostics on the same node
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
public override void Initialize(AnalysisContext context)
|
||||||
{
|
{
|
||||||
base.Initialize(context);
|
base.Initialize(context);
|
||||||
context.HandlePropertyDeclaration(Analyze);
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,47 +5,46 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class ParameterMustBeInsideCommandAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public ParameterMustBeInsideCommandAnalyzer()
|
||||||
public class ParameterMustBeInsideCommandAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
"Parameters must be defined inside commands",
|
||||||
|
$"This parameter must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`.")
|
||||||
{
|
{
|
||||||
public ParameterMustBeInsideCommandAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
"Parameters must be defined inside commands",
|
private void Analyze(
|
||||||
$"This parameter must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`.")
|
SyntaxNodeAnalysisContext context,
|
||||||
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
|
IPropertySymbol property)
|
||||||
|
{
|
||||||
|
if (property.ContainingType is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (property.ContainingType.IsAbstract)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!CommandParameterSymbol.IsParameterProperty(property))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var isInsideCommand = property
|
||||||
|
.ContainingType
|
||||||
|
.AllInterfaces
|
||||||
|
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface));
|
||||||
|
|
||||||
|
if (!isInsideCommand)
|
||||||
{
|
{
|
||||||
}
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
|
|
||||||
private void Analyze(
|
|
||||||
SyntaxNodeAnalysisContext context,
|
|
||||||
PropertyDeclarationSyntax propertyDeclaration,
|
|
||||||
IPropertySymbol property)
|
|
||||||
{
|
|
||||||
if (property.ContainingType is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (property.ContainingType.IsAbstract)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!CommandParameterSymbol.IsParameterProperty(property))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var isInsideCommand = property
|
|
||||||
.ContainingType
|
|
||||||
.AllInterfaces
|
|
||||||
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface));
|
|
||||||
|
|
||||||
if (!isInsideCommand)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
|
||||||
{
|
|
||||||
base.Initialize(context);
|
|
||||||
context.HandlePropertyDeclaration(Analyze);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
base.Initialize(context);
|
||||||
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,64 +5,63 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class ParameterMustBeLastIfNonScalarAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public ParameterMustBeLastIfNonScalarAnalyzer()
|
||||||
public class ParameterMustBeLastIfNonScalarAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
"Parameters of non-scalar types must be last in order",
|
||||||
|
"This parameter has a non-scalar type so it must be last in order (its order must be highest within the command).")
|
||||||
{
|
{
|
||||||
public ParameterMustBeLastIfNonScalarAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
"Parameters of non-scalar types must be last in order",
|
private static bool IsScalar(ITypeSymbol type) =>
|
||||||
"This parameter has a non-scalar type so it must be last in order (its order must be highest within the command).")
|
type.DisplayNameMatches("string") ||
|
||||||
|
type.DisplayNameMatches("System.String") ||
|
||||||
|
!type.AllInterfaces
|
||||||
|
.Select(i => i.ConstructedFrom)
|
||||||
|
.Any(s => s.DisplayNameMatches("System.Collections.Generic.IEnumerable<T>"));
|
||||||
|
|
||||||
|
private void Analyze(
|
||||||
|
SyntaxNodeAnalysisContext context,
|
||||||
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
|
IPropertySymbol property)
|
||||||
|
{
|
||||||
|
if (property.ContainingType is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (IsScalar(property.Type))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var parameter = CommandParameterSymbol.TryResolve(property);
|
||||||
|
if (parameter is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var otherProperties = property
|
||||||
|
.ContainingType
|
||||||
|
.GetMembers()
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.Where(m => !m.Equals(property, SymbolEqualityComparer.Default))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var otherProperty in otherProperties)
|
||||||
{
|
{
|
||||||
}
|
var otherParameter = CommandParameterSymbol.TryResolve(otherProperty);
|
||||||
|
if (otherParameter is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
private static bool IsScalar(ITypeSymbol type) =>
|
if (otherParameter.Order > parameter.Order)
|
||||||
type.DisplayNameMatches("string") ||
|
|
||||||
type.DisplayNameMatches("System.String") ||
|
|
||||||
!type.AllInterfaces
|
|
||||||
.Select(i => i.ConstructedFrom)
|
|
||||||
.Any(s => s.DisplayNameMatches("System.Collections.Generic.IEnumerable<T>"));
|
|
||||||
|
|
||||||
private void Analyze(
|
|
||||||
SyntaxNodeAnalysisContext context,
|
|
||||||
PropertyDeclarationSyntax propertyDeclaration,
|
|
||||||
IPropertySymbol property)
|
|
||||||
{
|
|
||||||
if (property.ContainingType is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (IsScalar(property.Type))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var parameter = CommandParameterSymbol.TryResolve(property);
|
|
||||||
if (parameter is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var otherProperties = property
|
|
||||||
.ContainingType
|
|
||||||
.GetMembers()
|
|
||||||
.OfType<IPropertySymbol>()
|
|
||||||
.Where(m => !m.Equals(property, SymbolEqualityComparer.Default))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var otherProperty in otherProperties)
|
|
||||||
{
|
{
|
||||||
var otherParameter = CommandParameterSymbol.TryResolve(otherProperty);
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
if (otherParameter is null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (otherParameter.Order > parameter.Order)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
public override void Initialize(AnalysisContext context)
|
||||||
{
|
{
|
||||||
base.Initialize(context);
|
base.Initialize(context);
|
||||||
context.HandlePropertyDeclaration(Analyze);
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,62 +5,61 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class ParameterMustBeSingleIfNonScalarAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public ParameterMustBeSingleIfNonScalarAnalyzer()
|
||||||
public class ParameterMustBeSingleIfNonScalarAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
"Parameters of non-scalar types are limited to one per command",
|
||||||
|
"This parameter has a non-scalar type so it must be the only such parameter in the command.")
|
||||||
{
|
{
|
||||||
public ParameterMustBeSingleIfNonScalarAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
"Parameters of non-scalar types are limited to one per command",
|
private static bool IsScalar(ITypeSymbol type) =>
|
||||||
"This parameter has a non-scalar type so it must be the only such parameter in the command.")
|
type.DisplayNameMatches("string") ||
|
||||||
|
type.DisplayNameMatches("System.String") ||
|
||||||
|
!type.AllInterfaces
|
||||||
|
.Select(i => i.ConstructedFrom)
|
||||||
|
.Any(s => s.DisplayNameMatches("System.Collections.Generic.IEnumerable<T>"));
|
||||||
|
|
||||||
|
private void Analyze(
|
||||||
|
SyntaxNodeAnalysisContext context,
|
||||||
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
|
IPropertySymbol property)
|
||||||
|
{
|
||||||
|
if (property.ContainingType is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!CommandParameterSymbol.IsParameterProperty(property))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (IsScalar(property.Type))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var otherProperties = property
|
||||||
|
.ContainingType
|
||||||
|
.GetMembers()
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.Where(m => !m.Equals(property, SymbolEqualityComparer.Default))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var otherProperty in otherProperties)
|
||||||
{
|
{
|
||||||
}
|
if (!CommandParameterSymbol.IsParameterProperty(otherProperty))
|
||||||
|
continue;
|
||||||
|
|
||||||
private static bool IsScalar(ITypeSymbol type) =>
|
if (!IsScalar(otherProperty.Type))
|
||||||
type.DisplayNameMatches("string") ||
|
|
||||||
type.DisplayNameMatches("System.String") ||
|
|
||||||
!type.AllInterfaces
|
|
||||||
.Select(i => i.ConstructedFrom)
|
|
||||||
.Any(s => s.DisplayNameMatches("System.Collections.Generic.IEnumerable<T>"));
|
|
||||||
|
|
||||||
private void Analyze(
|
|
||||||
SyntaxNodeAnalysisContext context,
|
|
||||||
PropertyDeclarationSyntax propertyDeclaration,
|
|
||||||
IPropertySymbol property)
|
|
||||||
{
|
|
||||||
if (property.ContainingType is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!CommandParameterSymbol.IsParameterProperty(property))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (IsScalar(property.Type))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var otherProperties = property
|
|
||||||
.ContainingType
|
|
||||||
.GetMembers()
|
|
||||||
.OfType<IPropertySymbol>()
|
|
||||||
.Where(m => !m.Equals(property, SymbolEqualityComparer.Default))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var otherProperty in otherProperties)
|
|
||||||
{
|
{
|
||||||
if (!CommandParameterSymbol.IsParameterProperty(otherProperty))
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!IsScalar(otherProperty.Type))
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
public override void Initialize(AnalysisContext context)
|
||||||
{
|
{
|
||||||
base.Initialize(context);
|
base.Initialize(context);
|
||||||
context.HandlePropertyDeclaration(Analyze);
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,60 +6,59 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class ParameterMustHaveUniqueNameAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public ParameterMustHaveUniqueNameAnalyzer()
|
||||||
public class ParameterMustHaveUniqueNameAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
"Parameters must have unique names",
|
||||||
|
"This parameter's name must be unique within the command (comparison IS NOT case sensitive).")
|
||||||
{
|
{
|
||||||
public ParameterMustHaveUniqueNameAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
"Parameters must have unique names",
|
private void Analyze(
|
||||||
"This parameter's name must be unique within the command (comparison IS NOT case sensitive).")
|
SyntaxNodeAnalysisContext context,
|
||||||
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
|
IPropertySymbol property)
|
||||||
|
{
|
||||||
|
if (property.ContainingType is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var parameter = CommandParameterSymbol.TryResolve(property);
|
||||||
|
if (parameter is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(parameter.Name))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var otherProperties = property
|
||||||
|
.ContainingType
|
||||||
|
.GetMembers()
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.Where(m => !m.Equals(property, SymbolEqualityComparer.Default))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var otherProperty in otherProperties)
|
||||||
{
|
{
|
||||||
}
|
var otherParameter = CommandParameterSymbol.TryResolve(otherProperty);
|
||||||
|
if (otherParameter is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
private void Analyze(
|
if (string.IsNullOrWhiteSpace(otherParameter.Name))
|
||||||
SyntaxNodeAnalysisContext context,
|
continue;
|
||||||
PropertyDeclarationSyntax propertyDeclaration,
|
|
||||||
IPropertySymbol property)
|
|
||||||
{
|
|
||||||
if (property.ContainingType is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var parameter = CommandParameterSymbol.TryResolve(property);
|
if (string.Equals(parameter.Name, otherParameter.Name, StringComparison.OrdinalIgnoreCase))
|
||||||
if (parameter is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(parameter.Name))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var otherProperties = property
|
|
||||||
.ContainingType
|
|
||||||
.GetMembers()
|
|
||||||
.OfType<IPropertySymbol>()
|
|
||||||
.Where(m => !m.Equals(property, SymbolEqualityComparer.Default))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var otherProperty in otherProperties)
|
|
||||||
{
|
{
|
||||||
var otherParameter = CommandParameterSymbol.TryResolve(otherProperty);
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
if (otherParameter is null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(otherParameter.Name))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (string.Equals(parameter.Name, otherParameter.Name, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
public override void Initialize(AnalysisContext context)
|
||||||
{
|
{
|
||||||
base.Initialize(context);
|
base.Initialize(context);
|
||||||
context.HandlePropertyDeclaration(Analyze);
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,54 +5,53 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class ParameterMustHaveUniqueOrderAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public ParameterMustHaveUniqueOrderAnalyzer()
|
||||||
public class ParameterMustHaveUniqueOrderAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
"Parameters must have unique order",
|
||||||
|
"This parameter's order must be unique within the command.")
|
||||||
{
|
{
|
||||||
public ParameterMustHaveUniqueOrderAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
"Parameters must have unique order",
|
private void Analyze(
|
||||||
"This parameter's order must be unique within the command.")
|
SyntaxNodeAnalysisContext context,
|
||||||
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
|
IPropertySymbol property)
|
||||||
|
{
|
||||||
|
if (property.ContainingType is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var parameter = CommandParameterSymbol.TryResolve(property);
|
||||||
|
if (parameter is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var otherProperties = property
|
||||||
|
.ContainingType
|
||||||
|
.GetMembers()
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.Where(m => !m.Equals(property, SymbolEqualityComparer.Default))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var otherProperty in otherProperties)
|
||||||
{
|
{
|
||||||
}
|
var otherParameter = CommandParameterSymbol.TryResolve(otherProperty);
|
||||||
|
if (otherParameter is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
private void Analyze(
|
if (parameter.Order == otherParameter.Order)
|
||||||
SyntaxNodeAnalysisContext context,
|
|
||||||
PropertyDeclarationSyntax propertyDeclaration,
|
|
||||||
IPropertySymbol property)
|
|
||||||
{
|
|
||||||
if (property.ContainingType is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var parameter = CommandParameterSymbol.TryResolve(property);
|
|
||||||
if (parameter is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var otherProperties = property
|
|
||||||
.ContainingType
|
|
||||||
.GetMembers()
|
|
||||||
.OfType<IPropertySymbol>()
|
|
||||||
.Where(m => !m.Equals(property, SymbolEqualityComparer.Default))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var otherProperty in otherProperties)
|
|
||||||
{
|
{
|
||||||
var otherParameter = CommandParameterSymbol.TryResolve(otherProperty);
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
if (otherParameter is null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (parameter.Order == otherParameter.Order)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
public override void Initialize(AnalysisContext context)
|
||||||
{
|
{
|
||||||
base.Initialize(context);
|
base.Initialize(context);
|
||||||
context.HandlePropertyDeclaration(Analyze);
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,46 +5,45 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class ParameterMustHaveValidConverterAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public ParameterMustHaveValidConverterAnalyzer()
|
||||||
public class ParameterMustHaveValidConverterAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
$"Parameter converters must derive from `{SymbolNames.CliFxBindingConverterClass}`",
|
||||||
|
$"Converter specified for this parameter must derive from `{SymbolNames.CliFxBindingConverterClass}`.")
|
||||||
{
|
{
|
||||||
public ParameterMustHaveValidConverterAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
$"Parameter converters must derive from `{SymbolNames.CliFxBindingConverterClass}`",
|
private void Analyze(
|
||||||
$"Converter specified for this parameter must derive from `{SymbolNames.CliFxBindingConverterClass}`.")
|
SyntaxNodeAnalysisContext context,
|
||||||
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
|
IPropertySymbol property)
|
||||||
|
{
|
||||||
|
var parameter = CommandParameterSymbol.TryResolve(property);
|
||||||
|
if (parameter is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (parameter.ConverterType is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// We check against an internal interface because checking against a generic class is a pain
|
||||||
|
var converterImplementsInterface = parameter
|
||||||
|
.ConverterType
|
||||||
|
.AllInterfaces
|
||||||
|
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingConverterInterface));
|
||||||
|
|
||||||
|
if (!converterImplementsInterface)
|
||||||
{
|
{
|
||||||
}
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
|
|
||||||
private void Analyze(
|
|
||||||
SyntaxNodeAnalysisContext context,
|
|
||||||
PropertyDeclarationSyntax propertyDeclaration,
|
|
||||||
IPropertySymbol property)
|
|
||||||
{
|
|
||||||
var parameter = CommandParameterSymbol.TryResolve(property);
|
|
||||||
if (parameter is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (parameter.ConverterType is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// We check against an internal interface because checking against a generic class is a pain
|
|
||||||
var converterImplementsInterface = parameter
|
|
||||||
.ConverterType
|
|
||||||
.AllInterfaces
|
|
||||||
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingConverterInterface));
|
|
||||||
|
|
||||||
if (!converterImplementsInterface)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
|
||||||
{
|
|
||||||
base.Initialize(context);
|
|
||||||
context.HandlePropertyDeclaration(Analyze);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
base.Initialize(context);
|
||||||
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,48 +5,47 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class ParameterMustHaveValidValidatorsAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public ParameterMustHaveValidValidatorsAnalyzer()
|
||||||
public class ParameterMustHaveValidValidatorsAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
$"Parameter validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`",
|
||||||
|
$"All validators specified for this parameter must derive from `{SymbolNames.CliFxBindingValidatorClass}`.")
|
||||||
{
|
{
|
||||||
public ParameterMustHaveValidValidatorsAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
$"Parameter validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`",
|
|
||||||
$"All validators specified for this parameter must derive from `{SymbolNames.CliFxBindingValidatorClass}`.")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Analyze(
|
private void Analyze(
|
||||||
SyntaxNodeAnalysisContext context,
|
SyntaxNodeAnalysisContext context,
|
||||||
PropertyDeclarationSyntax propertyDeclaration,
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
IPropertySymbol property)
|
IPropertySymbol property)
|
||||||
{
|
{
|
||||||
var parameter = CommandParameterSymbol.TryResolve(property);
|
var parameter = CommandParameterSymbol.TryResolve(property);
|
||||||
if (parameter is null)
|
if (parameter is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var validatorType in parameter.ValidatorTypes)
|
foreach (var validatorType in parameter.ValidatorTypes)
|
||||||
|
{
|
||||||
|
// We check against an internal interface because checking against a generic class is a pain
|
||||||
|
var validatorImplementsInterface = validatorType
|
||||||
|
.AllInterfaces
|
||||||
|
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingValidatorInterface));
|
||||||
|
|
||||||
|
if (!validatorImplementsInterface)
|
||||||
{
|
{
|
||||||
// We check against an internal interface because checking against a generic class is a pain
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
var validatorImplementsInterface = validatorType
|
|
||||||
.AllInterfaces
|
|
||||||
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingValidatorInterface));
|
|
||||||
|
|
||||||
if (!validatorImplementsInterface)
|
// No need to report multiple identical diagnostics on the same node
|
||||||
{
|
break;
|
||||||
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
|
||||||
|
|
||||||
// No need to report multiple identical diagnostics on the same node
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
public override void Initialize(AnalysisContext context)
|
||||||
{
|
{
|
||||||
base.Initialize(context);
|
base.Initialize(context);
|
||||||
context.HandlePropertyDeclaration(Analyze);
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,73 +6,72 @@ using Microsoft.CodeAnalysis.CSharp;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class SystemConsoleShouldBeAvoidedAnalyzer : AnalyzerBase
|
||||||
{
|
{
|
||||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
public SystemConsoleShouldBeAvoidedAnalyzer()
|
||||||
public class SystemConsoleShouldBeAvoidedAnalyzer : AnalyzerBase
|
: base(
|
||||||
|
$"Avoid calling `System.Console` where `{SymbolNames.CliFxConsoleInterface}` is available",
|
||||||
|
$"Use the provided `{SymbolNames.CliFxConsoleInterface}` abstraction instead of `System.Console` to ensure that the command can be tested in isolation.",
|
||||||
|
DiagnosticSeverity.Warning)
|
||||||
{
|
{
|
||||||
public SystemConsoleShouldBeAvoidedAnalyzer()
|
}
|
||||||
: base(
|
|
||||||
$"Avoid calling `System.Console` where `{SymbolNames.CliFxConsoleInterface}` is available",
|
|
||||||
$"Use the provided `{SymbolNames.CliFxConsoleInterface}` abstraction instead of `System.Console` to ensure that the command can be tested in isolation.",
|
|
||||||
DiagnosticSeverity.Warning)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private MemberAccessExpressionSyntax? TryGetSystemConsoleMemberAccess(
|
private MemberAccessExpressionSyntax? TryGetSystemConsoleMemberAccess(
|
||||||
SyntaxNodeAnalysisContext context,
|
SyntaxNodeAnalysisContext context,
|
||||||
SyntaxNode node)
|
SyntaxNode node)
|
||||||
{
|
{
|
||||||
var currentNode = node;
|
var currentNode = node;
|
||||||
|
|
||||||
while (currentNode is MemberAccessExpressionSyntax memberAccess)
|
while (currentNode is MemberAccessExpressionSyntax memberAccess)
|
||||||
|
{
|
||||||
|
var member = context.SemanticModel.GetSymbolInfo(memberAccess).Symbol;
|
||||||
|
|
||||||
|
if (member?.ContainingType?.DisplayNameMatches("System.Console") == true)
|
||||||
{
|
{
|
||||||
var member = context.SemanticModel.GetSymbolInfo(memberAccess).Symbol;
|
return memberAccess;
|
||||||
|
|
||||||
if (member?.ContainingType?.DisplayNameMatches("System.Console") == true)
|
|
||||||
{
|
|
||||||
return memberAccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get inner expression, which may be another member access expression.
|
|
||||||
// Example: System.Console.Error
|
|
||||||
// ~~~~~~~~~~~~~~ <- inner member access expression
|
|
||||||
// -------------------- <- outer member access expression
|
|
||||||
currentNode = memberAccess.Expression;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
// Get inner expression, which may be another member access expression.
|
||||||
|
// Example: System.Console.Error
|
||||||
|
// ~~~~~~~~~~~~~~ <- inner member access expression
|
||||||
|
// -------------------- <- outer member access expression
|
||||||
|
currentNode = memberAccess.Expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Analyze(SyntaxNodeAnalysisContext context)
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Analyze(SyntaxNodeAnalysisContext context)
|
||||||
|
{
|
||||||
|
// Try to get a member access on System.Console in the current expression,
|
||||||
|
// or in any of its inner expressions.
|
||||||
|
var systemConsoleMemberAccess = TryGetSystemConsoleMemberAccess(context, context.Node);
|
||||||
|
if (systemConsoleMemberAccess is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check if IConsole is available in scope as an alternative to System.Console
|
||||||
|
var isConsoleInterfaceAvailable = context
|
||||||
|
.Node
|
||||||
|
.Ancestors()
|
||||||
|
.OfType<MethodDeclarationSyntax>()
|
||||||
|
.SelectMany(m => m.ParameterList.Parameters)
|
||||||
|
.Select(p => p.Type)
|
||||||
|
.Select(t => context.SemanticModel.GetSymbolInfo(t).Symbol)
|
||||||
|
.Where(s => s is not null)
|
||||||
|
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxConsoleInterface));
|
||||||
|
|
||||||
|
if (isConsoleInterfaceAvailable)
|
||||||
{
|
{
|
||||||
// Try to get a member access on System.Console in the current expression,
|
context.ReportDiagnostic(CreateDiagnostic(systemConsoleMemberAccess.GetLocation()));
|
||||||
// or in any of its inner expressions.
|
|
||||||
var systemConsoleMemberAccess = TryGetSystemConsoleMemberAccess(context, context.Node);
|
|
||||||
if (systemConsoleMemberAccess is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Check if IConsole is available in scope as an alternative to System.Console
|
|
||||||
var isConsoleInterfaceAvailable = context
|
|
||||||
.Node
|
|
||||||
.Ancestors()
|
|
||||||
.OfType<MethodDeclarationSyntax>()
|
|
||||||
.SelectMany(m => m.ParameterList.Parameters)
|
|
||||||
.Select(p => p.Type)
|
|
||||||
.Select(t => context.SemanticModel.GetSymbolInfo(t).Symbol)
|
|
||||||
.Where(s => s is not null)
|
|
||||||
.Any(s => s.DisplayNameMatches(SymbolNames.CliFxConsoleInterface));
|
|
||||||
|
|
||||||
if (isConsoleInterfaceAvailable)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(CreateDiagnostic(systemConsoleMemberAccess.GetLocation()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize(AnalysisContext context)
|
|
||||||
{
|
|
||||||
base.Initialize(context);
|
|
||||||
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.SimpleMemberAccessExpression);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
base.Initialize(context);
|
||||||
|
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.SimpleMemberAccessExpression);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,50 +4,49 @@ using Microsoft.CodeAnalysis.CSharp;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Utils.Extensions
|
namespace CliFx.Analyzers.Utils.Extensions;
|
||||||
|
|
||||||
|
internal static class RoslynExtensions
|
||||||
{
|
{
|
||||||
internal static class RoslynExtensions
|
public static bool DisplayNameMatches(this ISymbol symbol, string name) =>
|
||||||
|
string.Equals(
|
||||||
|
// Fully qualified name, without `global::`
|
||||||
|
symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
|
||||||
|
name,
|
||||||
|
StringComparison.Ordinal
|
||||||
|
);
|
||||||
|
|
||||||
|
public static void HandleClassDeclaration(
|
||||||
|
this AnalysisContext analysisContext,
|
||||||
|
Action<SyntaxNodeAnalysisContext, ClassDeclarationSyntax, ITypeSymbol> analyze)
|
||||||
{
|
{
|
||||||
public static bool DisplayNameMatches(this ISymbol symbol, string name) =>
|
analysisContext.RegisterSyntaxNodeAction(ctx =>
|
||||||
string.Equals(
|
|
||||||
// Fully qualified name, without `global::`
|
|
||||||
symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
|
|
||||||
name,
|
|
||||||
StringComparison.Ordinal
|
|
||||||
);
|
|
||||||
|
|
||||||
public static void HandleClassDeclaration(
|
|
||||||
this AnalysisContext analysisContext,
|
|
||||||
Action<SyntaxNodeAnalysisContext, ClassDeclarationSyntax, ITypeSymbol> analyze)
|
|
||||||
{
|
{
|
||||||
analysisContext.RegisterSyntaxNodeAction(ctx =>
|
if (ctx.Node is not ClassDeclarationSyntax classDeclaration)
|
||||||
{
|
return;
|
||||||
if (ctx.Node is not ClassDeclarationSyntax classDeclaration)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var type = ctx.SemanticModel.GetDeclaredSymbol(classDeclaration);
|
var type = ctx.SemanticModel.GetDeclaredSymbol(classDeclaration);
|
||||||
if (type is null)
|
if (type is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
analyze(ctx, classDeclaration, type);
|
analyze(ctx, classDeclaration, type);
|
||||||
}, SyntaxKind.ClassDeclaration);
|
}, SyntaxKind.ClassDeclaration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void HandlePropertyDeclaration(
|
public static void HandlePropertyDeclaration(
|
||||||
this AnalysisContext analysisContext,
|
this AnalysisContext analysisContext,
|
||||||
Action<SyntaxNodeAnalysisContext, PropertyDeclarationSyntax, IPropertySymbol> analyze)
|
Action<SyntaxNodeAnalysisContext, PropertyDeclarationSyntax, IPropertySymbol> analyze)
|
||||||
|
{
|
||||||
|
analysisContext.RegisterSyntaxNodeAction(ctx =>
|
||||||
{
|
{
|
||||||
analysisContext.RegisterSyntaxNodeAction(ctx =>
|
if (ctx.Node is not PropertyDeclarationSyntax propertyDeclaration)
|
||||||
{
|
return;
|
||||||
if (ctx.Node is not PropertyDeclarationSyntax propertyDeclaration)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var property = ctx.SemanticModel.GetDeclaredSymbol(propertyDeclaration);
|
var property = ctx.SemanticModel.GetDeclaredSymbol(propertyDeclaration);
|
||||||
if (property is null)
|
if (property is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
analyze(ctx, propertyDeclaration, property);
|
analyze(ctx, propertyDeclaration, property);
|
||||||
}, SyntaxKind.PropertyDeclaration);
|
}, SyntaxKind.PropertyDeclaration);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace CliFx.Analyzers.Utils.Extensions
|
namespace CliFx.Analyzers.Utils.Extensions;
|
||||||
{
|
|
||||||
internal static class StringExtensions
|
|
||||||
{
|
|
||||||
public static string TrimEnd(
|
|
||||||
this string str,
|
|
||||||
string sub,
|
|
||||||
StringComparison comparison = StringComparison.Ordinal)
|
|
||||||
{
|
|
||||||
while (str.EndsWith(sub, comparison))
|
|
||||||
str = str.Substring(0, str.Length - sub.Length);
|
|
||||||
|
|
||||||
return str;
|
internal static class StringExtensions
|
||||||
}
|
{
|
||||||
|
public static string TrimEnd(
|
||||||
|
this string str,
|
||||||
|
string sub,
|
||||||
|
StringComparison comparison = StringComparison.Ordinal)
|
||||||
|
{
|
||||||
|
while (str.EndsWith(sub, comparison))
|
||||||
|
str = str.Substring(0, str.Length - sub.Length);
|
||||||
|
|
||||||
|
return str;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,30 +4,29 @@ using BenchmarkDotNet.Attributes;
|
|||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
|
|
||||||
namespace CliFx.Benchmarks
|
namespace CliFx.Benchmarks;
|
||||||
|
|
||||||
|
public partial class Benchmarks
|
||||||
{
|
{
|
||||||
public partial class Benchmarks
|
[Command]
|
||||||
|
public class CliFxCommand : ICommand
|
||||||
{
|
{
|
||||||
[Command]
|
[CommandOption("str", 's')]
|
||||||
public class CliFxCommand : ICommand
|
public string? StrOption { get; set; }
|
||||||
{
|
|
||||||
[CommandOption("str", 's')]
|
|
||||||
public string? StrOption { get; set; }
|
|
||||||
|
|
||||||
[CommandOption("int", 'i')]
|
[CommandOption("int", 'i')]
|
||||||
public int IntOption { get; set; }
|
public int IntOption { get; set; }
|
||||||
|
|
||||||
[CommandOption("bool", 'b')]
|
[CommandOption("bool", 'b')]
|
||||||
public bool BoolOption { get; set; }
|
public bool BoolOption { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "CliFx", Baseline = true)]
|
|
||||||
public async ValueTask<int> ExecuteWithCliFx() =>
|
|
||||||
await new CliApplicationBuilder()
|
|
||||||
.AddCommand<CliFxCommand>()
|
|
||||||
.Build()
|
|
||||||
.RunAsync(Arguments, new Dictionary<string, string>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Benchmark(Description = "CliFx", Baseline = true)]
|
||||||
|
public async ValueTask<int> ExecuteWithCliFx() =>
|
||||||
|
await new CliApplicationBuilder()
|
||||||
|
.AddCommand<CliFxCommand>()
|
||||||
|
.Build()
|
||||||
|
.RunAsync(Arguments, new Dictionary<string, string>());
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,26 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using clipr;
|
using clipr;
|
||||||
|
|
||||||
namespace CliFx.Benchmarks
|
namespace CliFx.Benchmarks;
|
||||||
|
|
||||||
|
public partial class Benchmarks
|
||||||
{
|
{
|
||||||
public partial class Benchmarks
|
public class CliprCommand
|
||||||
{
|
{
|
||||||
public class CliprCommand
|
[NamedArgument('s', "str")]
|
||||||
|
public string? StrOption { get; set; }
|
||||||
|
|
||||||
|
[NamedArgument('i', "int")]
|
||||||
|
public int IntOption { get; set; }
|
||||||
|
|
||||||
|
[NamedArgument('b', "bool", Constraint = NumArgsConstraint.Optional, Const = true)]
|
||||||
|
public bool BoolOption { get; set; }
|
||||||
|
|
||||||
|
public void Execute()
|
||||||
{
|
{
|
||||||
[NamedArgument('s', "str")]
|
|
||||||
public string? StrOption { get; set; }
|
|
||||||
|
|
||||||
[NamedArgument('i', "int")]
|
|
||||||
public int IntOption { get; set; }
|
|
||||||
|
|
||||||
[NamedArgument('b', "bool", Constraint = NumArgsConstraint.Optional, Const = true)]
|
|
||||||
public bool BoolOption { get; set; }
|
|
||||||
|
|
||||||
public void Execute()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark(Description = "Clipr")]
|
|
||||||
public void ExecuteWithClipr() => CliParser.Parse<CliprCommand>(Arguments).Execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Benchmark(Description = "Clipr")]
|
||||||
|
public void ExecuteWithClipr() => CliParser.Parse<CliprCommand>(Arguments).Execute();
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,23 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using Cocona;
|
using Cocona;
|
||||||
|
|
||||||
namespace CliFx.Benchmarks
|
namespace CliFx.Benchmarks;
|
||||||
{
|
|
||||||
public partial class Benchmarks
|
|
||||||
{
|
|
||||||
public class CoconaCommand
|
|
||||||
{
|
|
||||||
public void Execute(
|
|
||||||
[Option("str", new []{'s'})]
|
|
||||||
string? strOption,
|
|
||||||
[Option("int", new []{'i'})]
|
|
||||||
int intOption,
|
|
||||||
[Option("bool", new []{'b'})]
|
|
||||||
bool boolOption)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "Cocona")]
|
public partial class Benchmarks
|
||||||
public void ExecuteWithCocona() => CoconaApp.Run<CoconaCommand>(Arguments);
|
{
|
||||||
|
public class CoconaCommand
|
||||||
|
{
|
||||||
|
public void Execute(
|
||||||
|
[Option("str", new []{'s'})]
|
||||||
|
string? strOption,
|
||||||
|
[Option("int", new []{'i'})]
|
||||||
|
int intOption,
|
||||||
|
[Option("bool", new []{'b'})]
|
||||||
|
bool boolOption)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Benchmark(Description = "Cocona")]
|
||||||
|
public void ExecuteWithCocona() => CoconaApp.Run<CoconaCommand>(Arguments);
|
||||||
}
|
}
|
||||||
@@ -1,30 +1,29 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using CommandLine;
|
using CommandLine;
|
||||||
|
|
||||||
namespace CliFx.Benchmarks
|
namespace CliFx.Benchmarks;
|
||||||
|
|
||||||
|
public partial class Benchmarks
|
||||||
{
|
{
|
||||||
public partial class Benchmarks
|
public class CommandLineParserCommand
|
||||||
{
|
{
|
||||||
public class CommandLineParserCommand
|
[Option('s', "str")]
|
||||||
|
public string? StrOption { get; set; }
|
||||||
|
|
||||||
|
[Option('i', "int")]
|
||||||
|
public int IntOption { get; set; }
|
||||||
|
|
||||||
|
[Option('b', "bool")]
|
||||||
|
public bool BoolOption { get; set; }
|
||||||
|
|
||||||
|
public void Execute()
|
||||||
{
|
{
|
||||||
[Option('s', "str")]
|
|
||||||
public string? StrOption { get; set; }
|
|
||||||
|
|
||||||
[Option('i', "int")]
|
|
||||||
public int IntOption { get; set; }
|
|
||||||
|
|
||||||
[Option('b', "bool")]
|
|
||||||
public bool BoolOption { get; set; }
|
|
||||||
|
|
||||||
public void Execute()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark(Description = "CommandLineParser")]
|
|
||||||
public void ExecuteWithCommandLineParser() =>
|
|
||||||
new Parser()
|
|
||||||
.ParseArguments(Arguments, typeof(CommandLineParserCommand))
|
|
||||||
.WithParsed<CommandLineParserCommand>(c => c.Execute());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Benchmark(Description = "CommandLineParser")]
|
||||||
|
public void ExecuteWithCommandLineParser() =>
|
||||||
|
new Parser()
|
||||||
|
.ParseArguments(Arguments, typeof(CommandLineParserCommand))
|
||||||
|
.WithParsed<CommandLineParserCommand>(c => c.Execute());
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,24 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using McMaster.Extensions.CommandLineUtils;
|
using McMaster.Extensions.CommandLineUtils;
|
||||||
|
|
||||||
namespace CliFx.Benchmarks
|
namespace CliFx.Benchmarks;
|
||||||
|
|
||||||
|
public partial class Benchmarks
|
||||||
{
|
{
|
||||||
public partial class Benchmarks
|
public class McMasterCommand
|
||||||
{
|
{
|
||||||
public class McMasterCommand
|
[Option("--str|-s")]
|
||||||
{
|
public string? StrOption { get; set; }
|
||||||
[Option("--str|-s")]
|
|
||||||
public string? StrOption { get; set; }
|
|
||||||
|
|
||||||
[Option("--int|-i")]
|
[Option("--int|-i")]
|
||||||
public int IntOption { get; set; }
|
public int IntOption { get; set; }
|
||||||
|
|
||||||
[Option("--bool|-b")]
|
[Option("--bool|-b")]
|
||||||
public bool BoolOption { get; set; }
|
public bool BoolOption { get; set; }
|
||||||
|
|
||||||
public int OnExecute() => 0;
|
public int OnExecute() => 0;
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "McMaster.Extensions.CommandLineUtils")]
|
|
||||||
public int ExecuteWithMcMaster() => CommandLineApplication.Execute<McMasterCommand>(Arguments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Benchmark(Description = "McMaster.Extensions.CommandLineUtils")]
|
||||||
|
public int ExecuteWithMcMaster() => CommandLineApplication.Execute<McMasterCommand>(Arguments);
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,26 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using PowerArgs;
|
using PowerArgs;
|
||||||
|
|
||||||
namespace CliFx.Benchmarks
|
namespace CliFx.Benchmarks;
|
||||||
|
|
||||||
|
public partial class Benchmarks
|
||||||
{
|
{
|
||||||
public partial class Benchmarks
|
public class PowerArgsCommand
|
||||||
{
|
{
|
||||||
public class PowerArgsCommand
|
[ArgShortcut("--str"), ArgShortcut("-s")]
|
||||||
|
public string? StrOption { get; set; }
|
||||||
|
|
||||||
|
[ArgShortcut("--int"), ArgShortcut("-i")]
|
||||||
|
public int IntOption { get; set; }
|
||||||
|
|
||||||
|
[ArgShortcut("--bool"), ArgShortcut("-b")]
|
||||||
|
public bool BoolOption { get; set; }
|
||||||
|
|
||||||
|
public void Main()
|
||||||
{
|
{
|
||||||
[ArgShortcut("--str"), ArgShortcut("-s")]
|
|
||||||
public string? StrOption { get; set; }
|
|
||||||
|
|
||||||
[ArgShortcut("--int"), ArgShortcut("-i")]
|
|
||||||
public int IntOption { get; set; }
|
|
||||||
|
|
||||||
[ArgShortcut("--bool"), ArgShortcut("-b")]
|
|
||||||
public bool BoolOption { get; set; }
|
|
||||||
|
|
||||||
public void Main()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark(Description = "PowerArgs")]
|
|
||||||
public void ExecuteWithPowerArgs() => Args.InvokeMain<PowerArgsCommand>(Arguments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Benchmark(Description = "PowerArgs")]
|
||||||
|
public void ExecuteWithPowerArgs() => Args.InvokeMain<PowerArgsCommand>(Arguments);
|
||||||
}
|
}
|
||||||
@@ -3,42 +3,41 @@ using System.CommandLine.Invocation;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
|
|
||||||
namespace CliFx.Benchmarks
|
namespace CliFx.Benchmarks;
|
||||||
|
|
||||||
|
public partial class Benchmarks
|
||||||
{
|
{
|
||||||
public partial class Benchmarks
|
public class SystemCommandLineCommand
|
||||||
{
|
{
|
||||||
public class SystemCommandLineCommand
|
public static int ExecuteHandler(string s, int i, bool b) => 0;
|
||||||
|
|
||||||
|
public Task<int> ExecuteAsync(string[] args)
|
||||||
{
|
{
|
||||||
public static int ExecuteHandler(string s, int i, bool b) => 0;
|
var command = new RootCommand
|
||||||
|
|
||||||
public Task<int> ExecuteAsync(string[] args)
|
|
||||||
{
|
{
|
||||||
var command = new RootCommand
|
new Option(new[] {"--str", "-s"})
|
||||||
{
|
{
|
||||||
new Option(new[] {"--str", "-s"})
|
Argument = new Argument<string?>()
|
||||||
{
|
},
|
||||||
Argument = new Argument<string?>()
|
new Option(new[] {"--int", "-i"})
|
||||||
},
|
{
|
||||||
new Option(new[] {"--int", "-i"})
|
Argument = new Argument<int>()
|
||||||
{
|
},
|
||||||
Argument = new Argument<int>()
|
new Option(new[] {"--bool", "-b"})
|
||||||
},
|
{
|
||||||
new Option(new[] {"--bool", "-b"})
|
Argument = new Argument<bool>()
|
||||||
{
|
}
|
||||||
Argument = new Argument<bool>()
|
};
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
command.Handler = CommandHandler.Create(
|
command.Handler = CommandHandler.Create(
|
||||||
typeof(SystemCommandLineCommand).GetMethod(nameof(ExecuteHandler))!
|
typeof(SystemCommandLineCommand).GetMethod(nameof(ExecuteHandler))!
|
||||||
);
|
);
|
||||||
|
|
||||||
return command.InvokeAsync(args);
|
return command.InvokeAsync(args);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark(Description = "System.CommandLine")]
|
|
||||||
public async Task<int> ExecuteWithSystemCommandLine() =>
|
|
||||||
await new SystemCommandLineCommand().ExecuteAsync(Arguments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Benchmark(Description = "System.CommandLine")]
|
||||||
|
public async Task<int> ExecuteWithSystemCommandLine() =>
|
||||||
|
await new SystemCommandLineCommand().ExecuteAsync(Arguments);
|
||||||
}
|
}
|
||||||
@@ -3,18 +3,17 @@ using BenchmarkDotNet.Configs;
|
|||||||
using BenchmarkDotNet.Order;
|
using BenchmarkDotNet.Order;
|
||||||
using BenchmarkDotNet.Running;
|
using BenchmarkDotNet.Running;
|
||||||
|
|
||||||
namespace CliFx.Benchmarks
|
namespace CliFx.Benchmarks;
|
||||||
{
|
|
||||||
[RankColumn]
|
|
||||||
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
|
|
||||||
public partial class Benchmarks
|
|
||||||
{
|
|
||||||
private static readonly string[] Arguments = {"--str", "hello world", "-i", "13", "-b"};
|
|
||||||
|
|
||||||
public static void Main() => BenchmarkRunner.Run<Benchmarks>(
|
[RankColumn]
|
||||||
DefaultConfig
|
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
|
||||||
.Instance
|
public partial class Benchmarks
|
||||||
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
{
|
||||||
);
|
private static readonly string[] Arguments = {"--str", "hello world", "-i", "13", "-b"};
|
||||||
}
|
|
||||||
|
public static void Main() => BenchmarkRunner.Run<Benchmarks>(
|
||||||
|
DefaultConfig
|
||||||
|
.Instance
|
||||||
|
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||||
<PackageReference Include="clipr" Version="1.6.1" />
|
<PackageReference Include="clipr" Version="1.6.1" />
|
||||||
<PackageReference Include="Cocona" Version="1.5.0" />
|
<PackageReference Include="Cocona" Version="1.6.0" />
|
||||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="3.1.0" />
|
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="3.1.0" />
|
||||||
<PackageReference Include="PowerArgs" Version="3.6.0" />
|
<PackageReference Include="PowerArgs" Version="3.6.0" />
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -6,65 +6,64 @@ using CliFx.Demo.Utils;
|
|||||||
using CliFx.Exceptions;
|
using CliFx.Exceptions;
|
||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
|
|
||||||
namespace CliFx.Demo.Commands
|
namespace CliFx.Demo.Commands;
|
||||||
|
|
||||||
|
[Command("book add", Description = "Add a book to the library.")]
|
||||||
|
public partial class BookAddCommand : ICommand
|
||||||
{
|
{
|
||||||
[Command("book add", Description = "Add a book to the library.")]
|
private readonly LibraryProvider _libraryProvider;
|
||||||
public partial class BookAddCommand : ICommand
|
|
||||||
|
[CommandParameter(0, Name = "title", Description = "Book title.")]
|
||||||
|
public string Title { get; init; } = "";
|
||||||
|
|
||||||
|
[CommandOption("author", 'a', IsRequired = true, Description = "Book author.")]
|
||||||
|
public string Author { get; init; } = "";
|
||||||
|
|
||||||
|
[CommandOption("published", 'p', Description = "Book publish date.")]
|
||||||
|
public DateTimeOffset Published { get; init; } = CreateRandomDate();
|
||||||
|
|
||||||
|
[CommandOption("isbn", 'n', Description = "Book ISBN.")]
|
||||||
|
public Isbn Isbn { get; init; } = CreateRandomIsbn();
|
||||||
|
|
||||||
|
public BookAddCommand(LibraryProvider libraryProvider)
|
||||||
{
|
{
|
||||||
private readonly LibraryProvider _libraryProvider;
|
_libraryProvider = libraryProvider;
|
||||||
|
|
||||||
[CommandParameter(0, Name = "title", Description = "Book title.")]
|
|
||||||
public string Title { get; init; } = "";
|
|
||||||
|
|
||||||
[CommandOption("author", 'a', IsRequired = true, Description = "Book author.")]
|
|
||||||
public string Author { get; init; } = "";
|
|
||||||
|
|
||||||
[CommandOption("published", 'p', Description = "Book publish date.")]
|
|
||||||
public DateTimeOffset Published { get; init; } = CreateRandomDate();
|
|
||||||
|
|
||||||
[CommandOption("isbn", 'n', Description = "Book ISBN.")]
|
|
||||||
public Isbn Isbn { get; init; } = CreateRandomIsbn();
|
|
||||||
|
|
||||||
public BookAddCommand(LibraryProvider libraryProvider)
|
|
||||||
{
|
|
||||||
_libraryProvider = libraryProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
|
||||||
{
|
|
||||||
if (_libraryProvider.TryGetBook(Title) is not null)
|
|
||||||
throw new CommandException("Book already exists.", 10);
|
|
||||||
|
|
||||||
var book = new Book(Title, Author, Published, Isbn);
|
|
||||||
_libraryProvider.AddBook(book);
|
|
||||||
|
|
||||||
console.Output.WriteLine("Book added.");
|
|
||||||
console.Output.WriteBook(book);
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class BookAddCommand
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
private static readonly Random Random = new();
|
if (_libraryProvider.TryGetBook(Title) is not null)
|
||||||
|
throw new CommandException("Book already exists.", 10);
|
||||||
|
|
||||||
private static DateTimeOffset CreateRandomDate() => new(
|
var book = new Book(Title, Author, Published, Isbn);
|
||||||
Random.Next(1800, 2020),
|
_libraryProvider.AddBook(book);
|
||||||
Random.Next(1, 12),
|
|
||||||
Random.Next(1, 28),
|
|
||||||
Random.Next(1, 23),
|
|
||||||
Random.Next(1, 59),
|
|
||||||
Random.Next(1, 59),
|
|
||||||
TimeSpan.Zero
|
|
||||||
);
|
|
||||||
|
|
||||||
private static Isbn CreateRandomIsbn() => new(
|
console.Output.WriteLine("Book added.");
|
||||||
Random.Next(0, 999),
|
console.Output.WriteBook(book);
|
||||||
Random.Next(0, 99),
|
|
||||||
Random.Next(0, 99999),
|
return default;
|
||||||
Random.Next(0, 99),
|
|
||||||
Random.Next(0, 9)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class BookAddCommand
|
||||||
|
{
|
||||||
|
private static readonly Random Random = new();
|
||||||
|
|
||||||
|
private static DateTimeOffset CreateRandomDate() => new(
|
||||||
|
Random.Next(1800, 2020),
|
||||||
|
Random.Next(1, 12),
|
||||||
|
Random.Next(1, 28),
|
||||||
|
Random.Next(1, 23),
|
||||||
|
Random.Next(1, 59),
|
||||||
|
Random.Next(1, 59),
|
||||||
|
TimeSpan.Zero
|
||||||
|
);
|
||||||
|
|
||||||
|
private static Isbn CreateRandomIsbn() => new(
|
||||||
|
Random.Next(0, 999),
|
||||||
|
Random.Next(0, 99),
|
||||||
|
Random.Next(0, 99999),
|
||||||
|
Random.Next(0, 99),
|
||||||
|
Random.Next(0, 9)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -5,31 +5,30 @@ using CliFx.Demo.Utils;
|
|||||||
using CliFx.Exceptions;
|
using CliFx.Exceptions;
|
||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
|
|
||||||
namespace CliFx.Demo.Commands
|
namespace CliFx.Demo.Commands;
|
||||||
|
|
||||||
|
[Command("book", Description = "Retrieve a book from the library.")]
|
||||||
|
public class BookCommand : ICommand
|
||||||
{
|
{
|
||||||
[Command("book", Description = "Retrieve a book from the library.")]
|
private readonly LibraryProvider _libraryProvider;
|
||||||
public class BookCommand : ICommand
|
|
||||||
|
[CommandParameter(0, Name = "title", Description = "Title of the book to retrieve.")]
|
||||||
|
public string Title { get; init; } = "";
|
||||||
|
|
||||||
|
public BookCommand(LibraryProvider libraryProvider)
|
||||||
{
|
{
|
||||||
private readonly LibraryProvider _libraryProvider;
|
_libraryProvider = libraryProvider;
|
||||||
|
}
|
||||||
|
|
||||||
[CommandParameter(0, Name = "title", Description = "Title of the book to retrieve.")]
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
public string Title { get; init; } = "";
|
{
|
||||||
|
var book = _libraryProvider.TryGetBook(Title);
|
||||||
|
|
||||||
public BookCommand(LibraryProvider libraryProvider)
|
if (book is null)
|
||||||
{
|
throw new CommandException("Book not found.", 10);
|
||||||
_libraryProvider = libraryProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
console.Output.WriteBook(book);
|
||||||
{
|
|
||||||
var book = _libraryProvider.TryGetBook(Title);
|
|
||||||
|
|
||||||
if (book is null)
|
return default;
|
||||||
throw new CommandException("Book not found.", 10);
|
|
||||||
|
|
||||||
console.Output.WriteBook(book);
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,34 +4,33 @@ using CliFx.Demo.Domain;
|
|||||||
using CliFx.Demo.Utils;
|
using CliFx.Demo.Utils;
|
||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
|
|
||||||
namespace CliFx.Demo.Commands
|
namespace CliFx.Demo.Commands;
|
||||||
|
|
||||||
|
[Command("book list", Description = "List all books in the library.")]
|
||||||
|
public class BookListCommand : ICommand
|
||||||
{
|
{
|
||||||
[Command("book list", Description = "List all books in the library.")]
|
private readonly LibraryProvider _libraryProvider;
|
||||||
public class BookListCommand : ICommand
|
|
||||||
|
public BookListCommand(LibraryProvider libraryProvider)
|
||||||
{
|
{
|
||||||
private readonly LibraryProvider _libraryProvider;
|
_libraryProvider = libraryProvider;
|
||||||
|
}
|
||||||
|
|
||||||
public BookListCommand(LibraryProvider libraryProvider)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
|
{
|
||||||
|
var library = _libraryProvider.GetLibrary();
|
||||||
|
|
||||||
|
for (var i = 0; i < library.Books.Count; i++)
|
||||||
{
|
{
|
||||||
_libraryProvider = libraryProvider;
|
// Add margin
|
||||||
|
if (i != 0)
|
||||||
|
console.Output.WriteLine();
|
||||||
|
|
||||||
|
// Render book
|
||||||
|
var book = library.Books[i];
|
||||||
|
console.Output.WriteBook(book);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
return default;
|
||||||
{
|
|
||||||
var library = _libraryProvider.GetLibrary();
|
|
||||||
|
|
||||||
for (var i = 0; i < library.Books.Count; i++)
|
|
||||||
{
|
|
||||||
// Add margin
|
|
||||||
if (i != 0)
|
|
||||||
console.Output.WriteLine();
|
|
||||||
|
|
||||||
// Render book
|
|
||||||
var book = library.Books[i];
|
|
||||||
console.Output.WriteBook(book);
|
|
||||||
}
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,33 +4,32 @@ using CliFx.Demo.Domain;
|
|||||||
using CliFx.Exceptions;
|
using CliFx.Exceptions;
|
||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
|
|
||||||
namespace CliFx.Demo.Commands
|
namespace CliFx.Demo.Commands;
|
||||||
|
|
||||||
|
[Command("book remove", Description = "Remove a book from the library.")]
|
||||||
|
public class BookRemoveCommand : ICommand
|
||||||
{
|
{
|
||||||
[Command("book remove", Description = "Remove a book from the library.")]
|
private readonly LibraryProvider _libraryProvider;
|
||||||
public class BookRemoveCommand : ICommand
|
|
||||||
|
[CommandParameter(0, Name = "title", Description = "Title of the book to remove.")]
|
||||||
|
public string Title { get; init; } = "";
|
||||||
|
|
||||||
|
public BookRemoveCommand(LibraryProvider libraryProvider)
|
||||||
{
|
{
|
||||||
private readonly LibraryProvider _libraryProvider;
|
_libraryProvider = libraryProvider;
|
||||||
|
}
|
||||||
|
|
||||||
[CommandParameter(0, Name = "title", Description = "Title of the book to remove.")]
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
public string Title { get; init; } = "";
|
{
|
||||||
|
var book = _libraryProvider.TryGetBook(Title);
|
||||||
|
|
||||||
public BookRemoveCommand(LibraryProvider libraryProvider)
|
if (book is null)
|
||||||
{
|
throw new CommandException("Book not found.", 10);
|
||||||
_libraryProvider = libraryProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
_libraryProvider.RemoveBook(book);
|
||||||
{
|
|
||||||
var book = _libraryProvider.TryGetBook(Title);
|
|
||||||
|
|
||||||
if (book is null)
|
console.Output.WriteLine($"Book {Title} removed.");
|
||||||
throw new CommandException("Book not found.", 10);
|
|
||||||
|
|
||||||
_libraryProvider.RemoveBook(book);
|
return default;
|
||||||
|
|
||||||
console.Output.WriteLine($"Book {Title} removed.");
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,22 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace CliFx.Demo.Domain
|
namespace CliFx.Demo.Domain;
|
||||||
|
|
||||||
|
public class Book
|
||||||
{
|
{
|
||||||
public class Book
|
public string Title { get; }
|
||||||
|
|
||||||
|
public string Author { get; }
|
||||||
|
|
||||||
|
public DateTimeOffset Published { get; }
|
||||||
|
|
||||||
|
public Isbn Isbn { get; }
|
||||||
|
|
||||||
|
public Book(string title, string author, DateTimeOffset published, Isbn isbn)
|
||||||
{
|
{
|
||||||
public string Title { get; }
|
Title = title;
|
||||||
|
Author = author;
|
||||||
public string Author { get; }
|
Published = published;
|
||||||
|
Isbn = isbn;
|
||||||
public DateTimeOffset Published { get; }
|
|
||||||
|
|
||||||
public Isbn Isbn { get; }
|
|
||||||
|
|
||||||
public Book(string title, string author, DateTimeOffset published, Isbn isbn)
|
|
||||||
{
|
|
||||||
Title = title;
|
|
||||||
Author = author;
|
|
||||||
Published = published;
|
|
||||||
Isbn = isbn;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,45 +1,44 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace CliFx.Demo.Domain
|
namespace CliFx.Demo.Domain;
|
||||||
|
|
||||||
|
public partial class Isbn
|
||||||
{
|
{
|
||||||
public partial class Isbn
|
public int EanPrefix { get; }
|
||||||
|
|
||||||
|
public int RegistrationGroup { get; }
|
||||||
|
|
||||||
|
public int Registrant { get; }
|
||||||
|
|
||||||
|
public int Publication { get; }
|
||||||
|
|
||||||
|
public int CheckDigit { get; }
|
||||||
|
|
||||||
|
public Isbn(int eanPrefix, int registrationGroup, int registrant, int publication, int checkDigit)
|
||||||
{
|
{
|
||||||
public int EanPrefix { get; }
|
EanPrefix = eanPrefix;
|
||||||
|
RegistrationGroup = registrationGroup;
|
||||||
public int RegistrationGroup { get; }
|
Registrant = registrant;
|
||||||
|
Publication = publication;
|
||||||
public int Registrant { get; }
|
CheckDigit = checkDigit;
|
||||||
|
|
||||||
public int Publication { get; }
|
|
||||||
|
|
||||||
public int CheckDigit { get; }
|
|
||||||
|
|
||||||
public Isbn(int eanPrefix, int registrationGroup, int registrant, int publication, int checkDigit)
|
|
||||||
{
|
|
||||||
EanPrefix = eanPrefix;
|
|
||||||
RegistrationGroup = registrationGroup;
|
|
||||||
Registrant = registrant;
|
|
||||||
Publication = publication;
|
|
||||||
CheckDigit = checkDigit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() =>
|
|
||||||
$"{EanPrefix:000}-{RegistrationGroup:00}-{Registrant:00000}-{Publication:00}-{CheckDigit:0}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class Isbn
|
public override string ToString() =>
|
||||||
{
|
$"{EanPrefix:000}-{RegistrationGroup:00}-{Registrant:00000}-{Publication:00}-{CheckDigit:0}";
|
||||||
public static Isbn Parse(string value, IFormatProvider formatProvider)
|
}
|
||||||
{
|
|
||||||
var components = value.Split('-', 5, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
|
|
||||||
return new Isbn(
|
public partial class Isbn
|
||||||
int.Parse(components[0], formatProvider),
|
{
|
||||||
int.Parse(components[1], formatProvider),
|
public static Isbn Parse(string value, IFormatProvider formatProvider)
|
||||||
int.Parse(components[2], formatProvider),
|
{
|
||||||
int.Parse(components[3], formatProvider),
|
var components = value.Split('-', 5, StringSplitOptions.RemoveEmptyEntries);
|
||||||
int.Parse(components[4], formatProvider)
|
|
||||||
);
|
return new Isbn(
|
||||||
}
|
int.Parse(components[0], formatProvider),
|
||||||
|
int.Parse(components[1], formatProvider),
|
||||||
|
int.Parse(components[2], formatProvider),
|
||||||
|
int.Parse(components[3], formatProvider),
|
||||||
|
int.Parse(components[4], formatProvider)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,35 +2,34 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace CliFx.Demo.Domain
|
namespace CliFx.Demo.Domain;
|
||||||
|
|
||||||
|
public partial class Library
|
||||||
{
|
{
|
||||||
public partial class Library
|
public IReadOnlyList<Book> Books { get; }
|
||||||
|
|
||||||
|
public Library(IReadOnlyList<Book> books)
|
||||||
{
|
{
|
||||||
public IReadOnlyList<Book> Books { get; }
|
Books = books;
|
||||||
|
|
||||||
public Library(IReadOnlyList<Book> books)
|
|
||||||
{
|
|
||||||
Books = books;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Library WithBook(Book book)
|
|
||||||
{
|
|
||||||
var books = Books.ToList();
|
|
||||||
books.Add(book);
|
|
||||||
|
|
||||||
return new Library(books);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Library WithoutBook(Book book)
|
|
||||||
{
|
|
||||||
var books = Books.Where(b => b != book).ToArray();
|
|
||||||
|
|
||||||
return new Library(books);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class Library
|
public Library WithBook(Book book)
|
||||||
{
|
{
|
||||||
public static Library Empty { get; } = new(Array.Empty<Book>());
|
var books = Books.ToList();
|
||||||
|
books.Add(book);
|
||||||
|
|
||||||
|
return new Library(books);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Library WithoutBook(Book book)
|
||||||
|
{
|
||||||
|
var books = Books.Where(b => b != book).ToArray();
|
||||||
|
|
||||||
|
return new Library(books);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class Library
|
||||||
|
{
|
||||||
|
public static Library Empty { get; } = new(Array.Empty<Book>());
|
||||||
}
|
}
|
||||||
@@ -2,40 +2,39 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace CliFx.Demo.Domain
|
namespace CliFx.Demo.Domain;
|
||||||
|
|
||||||
|
public class LibraryProvider
|
||||||
{
|
{
|
||||||
public class LibraryProvider
|
private static string StorageFilePath { get; } = Path.Combine(Directory.GetCurrentDirectory(), "Library.json");
|
||||||
|
|
||||||
|
private void StoreLibrary(Library library)
|
||||||
{
|
{
|
||||||
private static string StorageFilePath { get; } = Path.Combine(Directory.GetCurrentDirectory(), "Library.json");
|
var data = JsonConvert.SerializeObject(library);
|
||||||
|
File.WriteAllText(StorageFilePath, data);
|
||||||
|
}
|
||||||
|
|
||||||
private void StoreLibrary(Library library)
|
public Library GetLibrary()
|
||||||
{
|
{
|
||||||
var data = JsonConvert.SerializeObject(library);
|
if (!File.Exists(StorageFilePath))
|
||||||
File.WriteAllText(StorageFilePath, data);
|
return Library.Empty;
|
||||||
}
|
|
||||||
|
|
||||||
public Library GetLibrary()
|
var data = File.ReadAllText(StorageFilePath);
|
||||||
{
|
|
||||||
if (!File.Exists(StorageFilePath))
|
|
||||||
return Library.Empty;
|
|
||||||
|
|
||||||
var data = File.ReadAllText(StorageFilePath);
|
return JsonConvert.DeserializeObject<Library>(data) ?? Library.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
return JsonConvert.DeserializeObject<Library>(data) ?? Library.Empty;
|
public Book? TryGetBook(string title) => GetLibrary().Books.FirstOrDefault(b => b.Title == title);
|
||||||
}
|
|
||||||
|
|
||||||
public Book? TryGetBook(string title) => GetLibrary().Books.FirstOrDefault(b => b.Title == title);
|
public void AddBook(Book book)
|
||||||
|
{
|
||||||
|
var updatedLibrary = GetLibrary().WithBook(book);
|
||||||
|
StoreLibrary(updatedLibrary);
|
||||||
|
}
|
||||||
|
|
||||||
public void AddBook(Book book)
|
public void RemoveBook(Book book)
|
||||||
{
|
{
|
||||||
var updatedLibrary = GetLibrary().WithBook(book);
|
var updatedLibrary = GetLibrary().WithoutBook(book);
|
||||||
StoreLibrary(updatedLibrary);
|
StoreLibrary(updatedLibrary);
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveBook(Book book)
|
|
||||||
{
|
|
||||||
var updatedLibrary = GetLibrary().WithoutBook(book);
|
|
||||||
StoreLibrary(updatedLibrary);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,36 +1,25 @@
|
|||||||
using System;
|
using CliFx;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Demo.Commands;
|
using CliFx.Demo.Commands;
|
||||||
using CliFx.Demo.Domain;
|
using CliFx.Demo.Domain;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace CliFx.Demo
|
// We use Microsoft.Extensions.DependencyInjection for injecting dependencies in commands
|
||||||
{
|
var services = new ServiceCollection();
|
||||||
public static class Program
|
|
||||||
{
|
|
||||||
private static IServiceProvider GetServiceProvider()
|
|
||||||
{
|
|
||||||
// We use Microsoft.Extensions.DependencyInjection for injecting dependencies in commands
|
|
||||||
var services = new ServiceCollection();
|
|
||||||
|
|
||||||
// Register services
|
// Register services
|
||||||
services.AddSingleton<LibraryProvider>();
|
services.AddSingleton<LibraryProvider>();
|
||||||
|
|
||||||
// Register commands
|
// Register commands
|
||||||
services.AddTransient<BookCommand>();
|
services.AddTransient<BookCommand>();
|
||||||
services.AddTransient<BookAddCommand>();
|
services.AddTransient<BookAddCommand>();
|
||||||
services.AddTransient<BookRemoveCommand>();
|
services.AddTransient<BookRemoveCommand>();
|
||||||
services.AddTransient<BookListCommand>();
|
services.AddTransient<BookListCommand>();
|
||||||
|
|
||||||
return services.BuildServiceProvider();
|
var serviceProvider = services.BuildServiceProvider();
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<int> Main() =>
|
return await new CliApplicationBuilder()
|
||||||
await new CliApplicationBuilder()
|
.SetDescription("Demo application showcasing CliFx features.")
|
||||||
.SetDescription("Demo application showcasing CliFx features.")
|
.AddCommandsFromThisAssembly()
|
||||||
.AddCommandsFromThisAssembly()
|
.UseTypeActivator(serviceProvider.GetRequiredService)
|
||||||
.UseTypeActivator(GetServiceProvider().GetRequiredService)
|
.Build()
|
||||||
.Build()
|
.RunAsync();
|
||||||
.RunAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,36 +2,35 @@
|
|||||||
using CliFx.Demo.Domain;
|
using CliFx.Demo.Domain;
|
||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
|
|
||||||
namespace CliFx.Demo.Utils
|
namespace CliFx.Demo.Utils;
|
||||||
|
|
||||||
|
internal static class ConsoleExtensions
|
||||||
{
|
{
|
||||||
internal static class ConsoleExtensions
|
public static void WriteBook(this ConsoleWriter writer, Book book)
|
||||||
{
|
{
|
||||||
public static void WriteBook(this ConsoleWriter writer, Book book)
|
// Title
|
||||||
{
|
using (writer.Console.WithForegroundColor(ConsoleColor.White))
|
||||||
// Title
|
writer.WriteLine(book.Title);
|
||||||
using (writer.Console.WithForegroundColor(ConsoleColor.White))
|
|
||||||
writer.WriteLine(book.Title);
|
|
||||||
|
|
||||||
// Author
|
// Author
|
||||||
writer.Write(" ");
|
writer.Write(" ");
|
||||||
writer.Write("Author: ");
|
writer.Write("Author: ");
|
||||||
|
|
||||||
using (writer.Console.WithForegroundColor(ConsoleColor.White))
|
using (writer.Console.WithForegroundColor(ConsoleColor.White))
|
||||||
writer.WriteLine(book.Author);
|
writer.WriteLine(book.Author);
|
||||||
|
|
||||||
// Published
|
// Published
|
||||||
writer.Write(" ");
|
writer.Write(" ");
|
||||||
writer.Write("Published: ");
|
writer.Write("Published: ");
|
||||||
|
|
||||||
using (writer.Console.WithForegroundColor(ConsoleColor.White))
|
using (writer.Console.WithForegroundColor(ConsoleColor.White))
|
||||||
writer.WriteLine($"{book.Published:d}");
|
writer.WriteLine($"{book.Published:d}");
|
||||||
|
|
||||||
// ISBN
|
// ISBN
|
||||||
writer.Write(" ");
|
writer.Write(" ");
|
||||||
writer.Write("ISBN: ");
|
writer.Write("ISBN: ");
|
||||||
|
|
||||||
using (writer.Console.WithForegroundColor(ConsoleColor.White))
|
using (writer.Console.WithForegroundColor(ConsoleColor.White))
|
||||||
writer.WriteLine(book.Isbn);
|
writer.WriteLine(book.Isbn);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -3,22 +3,21 @@ using System.Threading.Tasks;
|
|||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
|
|
||||||
namespace CliFx.Tests.Dummy.Commands
|
namespace CliFx.Tests.Dummy.Commands;
|
||||||
|
|
||||||
|
[Command("console-test")]
|
||||||
|
public class ConsoleTestCommand : ICommand
|
||||||
{
|
{
|
||||||
[Command("console-test")]
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
public class ConsoleTestCommand : ICommand
|
|
||||||
{
|
{
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
var input = console.Input.ReadToEnd();
|
||||||
|
|
||||||
|
using (console.WithColors(ConsoleColor.Black, ConsoleColor.White))
|
||||||
{
|
{
|
||||||
var input = console.Input.ReadToEnd();
|
console.Output.WriteLine(input);
|
||||||
|
console.Error.WriteLine(input);
|
||||||
using (console.WithColors(ConsoleColor.Black, ConsoleColor.White))
|
|
||||||
{
|
|
||||||
console.Output.WriteLine(input);
|
|
||||||
console.Error.WriteLine(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,19 +2,18 @@
|
|||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
|
|
||||||
namespace CliFx.Tests.Dummy.Commands
|
namespace CliFx.Tests.Dummy.Commands;
|
||||||
|
|
||||||
|
[Command("env-test")]
|
||||||
|
public class EnvironmentTestCommand : ICommand
|
||||||
{
|
{
|
||||||
[Command("env-test")]
|
[CommandOption("target", EnvironmentVariable = "ENV_TARGET")]
|
||||||
public class EnvironmentTestCommand : ICommand
|
public string GreetingTarget { get; set; } = "World";
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
[CommandOption("target", EnvironmentVariable = "ENV_TARGET")]
|
console.Output.WriteLine($"Hello {GreetingTarget}!");
|
||||||
public string GreetingTarget { get; set; } = "World";
|
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
return default;
|
||||||
{
|
|
||||||
console.Output.WriteLine($"Hello {GreetingTarget}!");
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,22 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace CliFx.Tests.Dummy
|
namespace CliFx.Tests.Dummy;
|
||||||
|
// This dummy application is used in tests for scenarios
|
||||||
|
// that require an external process to properly verify.
|
||||||
|
|
||||||
|
public static partial class Program
|
||||||
{
|
{
|
||||||
// This dummy application is used in tests for scenarios
|
public static Assembly Assembly { get; } = typeof(Program).Assembly;
|
||||||
// that require an external process to properly verify.
|
|
||||||
|
|
||||||
public static partial class Program
|
public static string Location { get; } = Assembly.Location;
|
||||||
{
|
}
|
||||||
public static Assembly Assembly { get; } = typeof(Program).Assembly;
|
|
||||||
|
|
||||||
public static string Location { get; } = Assembly.Location;
|
public static partial class Program
|
||||||
}
|
{
|
||||||
|
public static async Task Main() =>
|
||||||
public static partial class Program
|
await new CliApplicationBuilder()
|
||||||
{
|
.AddCommandsFromThisAssembly()
|
||||||
public static async Task Main() =>
|
.Build()
|
||||||
await new CliApplicationBuilder()
|
.RunAsync();
|
||||||
.AddCommandsFromThisAssembly()
|
|
||||||
.Build()
|
|
||||||
.RunAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -6,81 +6,80 @@ using FluentAssertions;
|
|||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace CliFx.Tests
|
namespace CliFx.Tests;
|
||||||
|
|
||||||
|
public class ApplicationSpecs : SpecsBase
|
||||||
{
|
{
|
||||||
public class ApplicationSpecs : SpecsBase
|
public ApplicationSpecs(ITestOutputHelper testOutput)
|
||||||
|
: base(testOutput)
|
||||||
{
|
{
|
||||||
public ApplicationSpecs(ITestOutputHelper testOutput)
|
}
|
||||||
: base(testOutput)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Application_can_be_created_with_minimal_configuration()
|
public async Task Application_can_be_created_with_minimal_configuration()
|
||||||
{
|
{
|
||||||
// Act
|
// Act
|
||||||
var app = new CliApplicationBuilder()
|
var app = new CliApplicationBuilder()
|
||||||
.AddCommandsFromThisAssembly()
|
.AddCommandsFromThisAssembly()
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var exitCode = await app.RunAsync(
|
var exitCode = await app.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Application_can_be_created_with_a_fully_customized_configuration()
|
public async Task Application_can_be_created_with_a_fully_customized_configuration()
|
||||||
{
|
{
|
||||||
// Act
|
// Act
|
||||||
var app = new CliApplicationBuilder()
|
var app = new CliApplicationBuilder()
|
||||||
.AddCommand<NoOpCommand>()
|
.AddCommand<NoOpCommand>()
|
||||||
.AddCommandsFrom(typeof(NoOpCommand).Assembly)
|
.AddCommandsFrom(typeof(NoOpCommand).Assembly)
|
||||||
.AddCommands(new[] {typeof(NoOpCommand)})
|
.AddCommands(new[] {typeof(NoOpCommand)})
|
||||||
.AddCommandsFrom(new[] {typeof(NoOpCommand).Assembly})
|
.AddCommandsFrom(new[] {typeof(NoOpCommand).Assembly})
|
||||||
.AddCommandsFromThisAssembly()
|
.AddCommandsFromThisAssembly()
|
||||||
.AllowDebugMode()
|
.AllowDebugMode()
|
||||||
.AllowPreviewMode()
|
.AllowPreviewMode()
|
||||||
.SetTitle("test")
|
.SetTitle("test")
|
||||||
.SetExecutableName("test")
|
.SetExecutableName("test")
|
||||||
.SetVersion("test")
|
.SetVersion("test")
|
||||||
.SetDescription("test")
|
.SetDescription("test")
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.UseTypeActivator(Activator.CreateInstance!)
|
.UseTypeActivator(Activator.CreateInstance!)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var exitCode = await app.RunAsync(
|
var exitCode = await app.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Application_configuration_fails_if_an_invalid_command_is_registered()
|
public async Task Application_configuration_fails_if_an_invalid_command_is_registered()
|
||||||
{
|
{
|
||||||
// Act
|
// Act
|
||||||
var app = new CliApplicationBuilder()
|
var app = new CliApplicationBuilder()
|
||||||
.AddCommand(typeof(ApplicationSpecs))
|
.AddCommand(typeof(ApplicationSpecs))
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var exitCode = await app.RunAsync(
|
var exitCode = await app.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdErr.Should().Contain("not a valid command");
|
stdErr.Should().Contain("not a valid command");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,22 +6,22 @@ using FluentAssertions;
|
|||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace CliFx.Tests
|
namespace CliFx.Tests;
|
||||||
{
|
|
||||||
public class CancellationSpecs : SpecsBase
|
|
||||||
{
|
|
||||||
public CancellationSpecs(ITestOutputHelper testOutput)
|
|
||||||
: base(testOutput)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
public class CancellationSpecs : SpecsBase
|
||||||
public async Task Command_can_register_to_receive_a_cancellation_signal_from_the_console()
|
{
|
||||||
{
|
public CancellationSpecs(ITestOutputHelper testOutput)
|
||||||
// Arrange
|
: base(testOutput)
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
{
|
||||||
// language=cs
|
}
|
||||||
@"
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Command_can_register_to_receive_a_cancellation_signal_from_the_console()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
|
// language=cs
|
||||||
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -44,24 +44,23 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
FakeConsole.RequestCancellation(TimeSpan.FromSeconds(0.2));
|
FakeConsole.RequestCancellation(TimeSpan.FromSeconds(0.2));
|
||||||
|
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdOut.Trim().Should().Be("Cancelled");
|
stdOut.Trim().Should().Be("Cancelled");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<IsTestProject>true</IsTestProject>
|
<IsTestProject>true</IsTestProject>
|
||||||
<CollectCoverage>true</CollectCoverage>
|
<CollectCoverage>true</CollectCoverage>
|
||||||
@@ -13,15 +13,15 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Basic.Reference.Assemblies" Version="1.1.2" />
|
<PackageReference Include="Basic.Reference.Assemblies" Version="1.2.4" />
|
||||||
<PackageReference Include="CliWrap" Version="3.3.2" />
|
<PackageReference Include="CliWrap" Version="3.3.3" />
|
||||||
<PackageReference Include="FluentAssertions" Version="5.10.3" />
|
<PackageReference Include="FluentAssertions" Version="6.2.0" />
|
||||||
<PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" />
|
<PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" PrivateAssets="all" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" PrivateAssets="all" />
|
||||||
<PackageReference Include="coverlet.msbuild" Version="3.0.3" PrivateAssets="all" />
|
<PackageReference Include="coverlet.msbuild" Version="3.1.0" PrivateAssets="all" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -11,42 +11,42 @@ using FluentAssertions;
|
|||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace CliFx.Tests
|
namespace CliFx.Tests;
|
||||||
|
|
||||||
|
public class ConsoleSpecs : SpecsBase
|
||||||
{
|
{
|
||||||
public class ConsoleSpecs : SpecsBase
|
public ConsoleSpecs(ITestOutputHelper testOutput)
|
||||||
|
: base(testOutput)
|
||||||
{
|
{
|
||||||
public ConsoleSpecs(ITestOutputHelper testOutput)
|
}
|
||||||
: base(testOutput)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Real_console_maps_directly_to_system_console()
|
public async Task Real_console_maps_directly_to_system_console()
|
||||||
{
|
{
|
||||||
// Can't verify our own console output, so using an
|
// Can't verify our own console output, so using an
|
||||||
// external process for this test.
|
// external process for this test.
|
||||||
|
|
||||||
// Arrange
|
// Arrange
|
||||||
var command = "Hello world" | Cli.Wrap("dotnet")
|
var command = "Hello world" | Cli.Wrap("dotnet")
|
||||||
.WithArguments(a => a
|
.WithArguments(a => a
|
||||||
.Add(Dummy.Program.Location)
|
.Add(Dummy.Program.Location)
|
||||||
.Add("console-test"));
|
.Add("console-test"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await command.ExecuteBufferedAsync();
|
var result = await command.ExecuteBufferedAsync();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
result.StandardOutput.Trim().Should().Be("Hello world");
|
result.StandardOutput.Trim().Should().Be("Hello world");
|
||||||
result.StandardError.Trim().Should().Be("Hello world");
|
result.StandardError.Trim().Should().Be("Hello world");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Fake_console_does_not_leak_to_system_console()
|
public async Task Fake_console_does_not_leak_to_system_console()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -66,43 +66,43 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
|
|
||||||
Console.OpenStandardInput().Should().NotBe(FakeConsole.Input.BaseStream);
|
Console.OpenStandardInput().Should().NotBeSameAs(FakeConsole.Input.BaseStream);
|
||||||
Console.OpenStandardOutput().Should().NotBe(FakeConsole.Output.BaseStream);
|
Console.OpenStandardOutput().Should().NotBeSameAs(FakeConsole.Output.BaseStream);
|
||||||
Console.OpenStandardError().Should().NotBe(FakeConsole.Error.BaseStream);
|
Console.OpenStandardError().Should().NotBeSameAs(FakeConsole.Error.BaseStream);
|
||||||
|
|
||||||
Console.ForegroundColor.Should().NotBe(ConsoleColor.DarkMagenta);
|
Console.ForegroundColor.Should().NotBe(ConsoleColor.DarkMagenta);
|
||||||
Console.BackgroundColor.Should().NotBe(ConsoleColor.DarkMagenta);
|
Console.BackgroundColor.Should().NotBe(ConsoleColor.DarkMagenta);
|
||||||
|
|
||||||
// This fails because tests don't spawn a console window
|
// This fails because tests don't spawn a console window
|
||||||
//Console.CursorLeft.Should().NotBe(42);
|
//Console.CursorLeft.Should().NotBe(42);
|
||||||
//Console.CursorTop.Should().NotBe(24);
|
//Console.CursorTop.Should().NotBe(24);
|
||||||
|
|
||||||
FakeConsole.IsInputRedirected.Should().BeTrue();
|
FakeConsole.IsInputRedirected.Should().BeTrue();
|
||||||
FakeConsole.IsOutputRedirected.Should().BeTrue();
|
FakeConsole.IsOutputRedirected.Should().BeTrue();
|
||||||
FakeConsole.IsErrorRedirected.Should().BeTrue();
|
FakeConsole.IsErrorRedirected.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Fake_console_can_be_used_with_an_in_memory_backing_store()
|
public async Task Fake_console_can_be_used_with_an_in_memory_backing_store()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -117,43 +117,42 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
FakeConsole.WriteInput("Hello world");
|
FakeConsole.WriteInput("Hello world");
|
||||||
|
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Trim().Should().Be("Hello world");
|
stdOut.Trim().Should().Be("Hello world");
|
||||||
stdErr.Trim().Should().Be("Hello world");
|
stdErr.Trim().Should().Be("Hello world");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Console_does_not_emit_preamble_when_used_with_encoding_that_has_it()
|
public void Console_does_not_emit_preamble_when_used_with_encoding_that_has_it()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
using var buffer = new MemoryStream();
|
using var buffer = new MemoryStream();
|
||||||
using var consoleWriter = new ConsoleWriter(FakeConsole, buffer, Encoding.UTF8);
|
using var consoleWriter = new ConsoleWriter(FakeConsole, buffer, Encoding.UTF8);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
consoleWriter.Write("Hello world");
|
consoleWriter.Write("Hello world");
|
||||||
consoleWriter.Flush();
|
consoleWriter.Flush();
|
||||||
|
|
||||||
var output = consoleWriter.Encoding.GetString(buffer.ToArray());
|
var output = consoleWriter.Encoding.GetString(buffer.ToArray());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
output.Should().Be("Hello world");
|
output.Should().Be("Hello world");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -10,71 +10,71 @@ using FluentAssertions;
|
|||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace CliFx.Tests
|
namespace CliFx.Tests;
|
||||||
|
|
||||||
|
public class DirectivesSpecs : SpecsBase
|
||||||
{
|
{
|
||||||
public class DirectivesSpecs : SpecsBase
|
public DirectivesSpecs(ITestOutputHelper testOutput)
|
||||||
|
: base(testOutput)
|
||||||
{
|
{
|
||||||
public DirectivesSpecs(ITestOutputHelper testOutput)
|
}
|
||||||
: base(testOutput)
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Debug_directive_can_be_specified_to_interrupt_execution_until_a_debugger_is_attached()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stdOutBuffer = new StringBuilder();
|
||||||
|
|
||||||
|
var command = Cli.Wrap("dotnet")
|
||||||
|
.WithArguments(a => a
|
||||||
|
.Add(Dummy.Program.Location)
|
||||||
|
.Add("[debug]")) | stdOutBuffer;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
try
|
||||||
{
|
{
|
||||||
}
|
// This has a timeout just in case the execution hangs forever
|
||||||
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
[Fact]
|
var task = command.ExecuteAsync(cts.Token);
|
||||||
public async Task Debug_directive_can_be_specified_to_interrupt_execution_until_a_debugger_is_attached()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var stdOutBuffer = new StringBuilder();
|
|
||||||
|
|
||||||
var command = Cli.Wrap("dotnet")
|
// We can't attach a debugger programmatically, so the application
|
||||||
.WithArguments(a => a
|
// will hang indefinitely.
|
||||||
.Add(Dummy.Program.Location)
|
// To work around it, we will wait until the application writes
|
||||||
.Add("[debug]")) | stdOutBuffer;
|
// something to the standard output and then kill it.
|
||||||
|
while (true)
|
||||||
// Act
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
// This has a timeout just in case the execution hangs forever
|
if (stdOutBuffer.Length > 0)
|
||||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
|
||||||
|
|
||||||
var task = command.ExecuteAsync(cts.Token);
|
|
||||||
|
|
||||||
// We can't attach a debugger programmatically, so the application
|
|
||||||
// will hang indefinitely.
|
|
||||||
// To work around it, we will wait until the application writes
|
|
||||||
// something to the standard output and then kill it.
|
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
if (stdOutBuffer.Length > 0)
|
cts.Cancel();
|
||||||
{
|
break;
|
||||||
cts.Cancel();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Delay(100, cts.Token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await task;
|
await Task.Delay(100, cts.Token);
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
// It's expected to fail
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var stdOut = stdOutBuffer.ToString();
|
await task;
|
||||||
|
}
|
||||||
// Assert
|
catch (OperationCanceledException)
|
||||||
stdOut.Should().Contain("Attach debugger to");
|
{
|
||||||
|
// It's expected to fail
|
||||||
TestOutput.WriteLine(stdOut);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
var stdOut = stdOutBuffer.ToString();
|
||||||
public async Task Preview_directive_can_be_specified_to_print_command_input()
|
|
||||||
{
|
// Assert
|
||||||
// Arrange
|
stdOut.Should().Contain("Attach debugger to");
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
|
||||||
// language=cs
|
TestOutput.WriteLine(stdOut);
|
||||||
@"
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Preview_directive_can_be_specified_to_print_command_input()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
|
// language=cs
|
||||||
|
@"
|
||||||
[Command(""cmd"")]
|
[Command(""cmd"")]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -82,31 +82,30 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.AllowPreviewMode()
|
.AllowPreviewMode()
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"[preview]", "cmd", "param", "-abc", "--option", "foo"},
|
new[] {"[preview]", "cmd", "param", "-abc", "--option", "foo"},
|
||||||
new Dictionary<string, string>
|
new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
["ENV_QOP"] = "hello",
|
["ENV_QOP"] = "hello",
|
||||||
["ENV_KIL"] = "world"
|
["ENV_KIL"] = "world"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Should().ContainAllInOrder(
|
stdOut.Should().ContainAllInOrder(
|
||||||
"cmd", "<param>", "[-a]", "[-b]", "[-c]", "[--option \"foo\"]",
|
"cmd", "<param>", "[-a]", "[-b]", "[-c]", "[--option \"foo\"]",
|
||||||
"ENV_QOP", "=", "\"hello\"",
|
"ENV_QOP", "=", "\"hello\"",
|
||||||
"ENV_KIL", "=", "\"world\""
|
"ENV_KIL", "=", "\"world\""
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,22 +10,22 @@ using FluentAssertions;
|
|||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace CliFx.Tests
|
namespace CliFx.Tests;
|
||||||
{
|
|
||||||
public class EnvironmentSpecs : SpecsBase
|
|
||||||
{
|
|
||||||
public EnvironmentSpecs(ITestOutputHelper testOutput)
|
|
||||||
: base(testOutput)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
public class EnvironmentSpecs : SpecsBase
|
||||||
public async Task Option_can_fall_back_to_an_environment_variable()
|
{
|
||||||
{
|
public EnvironmentSpecs(ITestOutputHelper testOutput)
|
||||||
// Arrange
|
: base(testOutput)
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
{
|
||||||
// language=cs
|
}
|
||||||
@"
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Option_can_fall_back_to_an_environment_variable()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
|
// language=cs
|
||||||
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -40,34 +40,34 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>
|
new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
["ENV_FOO"] = "bar"
|
["ENV_FOO"] = "bar"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Trim().Should().Be("bar");
|
stdOut.Trim().Should().Be("bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_does_not_fall_back_to_an_environment_variable_if_a_value_is_provided_through_arguments()
|
public async Task Option_does_not_fall_back_to_an_environment_variable_if_a_value_is_provided_through_arguments()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -82,34 +82,34 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"--foo", "baz"},
|
new[] {"--foo", "baz"},
|
||||||
new Dictionary<string, string>
|
new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
["ENV_FOO"] = "bar"
|
["ENV_FOO"] = "bar"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Trim().Should().Be("baz");
|
stdOut.Trim().Should().Be("baz");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_of_non_scalar_type_can_receive_multiple_values_from_an_environment_variable()
|
public async Task Option_of_non_scalar_type_can_receive_multiple_values_from_an_environment_variable()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -126,37 +126,37 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>
|
new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
["ENV_FOO"] = $"bar{Path.PathSeparator}baz"
|
["ENV_FOO"] = $"bar{Path.PathSeparator}baz"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Should().ConsistOfLines(
|
stdOut.Should().ConsistOfLines(
|
||||||
"bar",
|
"bar",
|
||||||
"baz"
|
"baz"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_of_scalar_type_always_receives_a_single_value_from_an_environment_variable()
|
public async Task Option_of_scalar_type_always_receives_a_single_value_from_an_environment_variable()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -171,34 +171,34 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>
|
new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
["ENV_FOO"] = $"bar{Path.PathSeparator}baz"
|
["ENV_FOO"] = $"bar{Path.PathSeparator}baz"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Trim().Should().Be($"bar{Path.PathSeparator}baz");
|
stdOut.Trim().Should().Be($"bar{Path.PathSeparator}baz");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Environment_variables_are_matched_case_sensitively()
|
public async Task Environment_variables_are_matched_case_sensitively()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -213,48 +213,47 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>
|
new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
["ENV_foo"] = "baz",
|
["ENV_foo"] = "baz",
|
||||||
["ENV_FOO"] = "bar",
|
["ENV_FOO"] = "bar",
|
||||||
["env_FOO"] = "qop"
|
["env_FOO"] = "qop"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Trim().Should().Be("bar");
|
stdOut.Trim().Should().Be("bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Environment_variables_are_extracted_automatically()
|
public async Task Environment_variables_are_extracted_automatically()
|
||||||
{
|
{
|
||||||
// Ensures that the environment variables are properly obtained from
|
// Ensures that the environment variables are properly obtained from
|
||||||
// System.Environment when they are not provided explicitly to CliApplication.
|
// System.Environment when they are not provided explicitly to CliApplication.
|
||||||
|
|
||||||
// Arrange
|
// Arrange
|
||||||
var command = Cli.Wrap("dotnet")
|
var command = Cli.Wrap("dotnet")
|
||||||
.WithArguments(a => a
|
.WithArguments(a => a
|
||||||
.Add(Dummy.Program.Location)
|
.Add(Dummy.Program.Location)
|
||||||
.Add("env-test"))
|
.Add("env-test"))
|
||||||
.WithEnvironmentVariables(e => e
|
.WithEnvironmentVariables(e => e
|
||||||
.Set("ENV_TARGET", "Mars"));
|
.Set("ENV_TARGET", "Mars"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await command.ExecuteBufferedAsync();
|
var result = await command.ExecuteBufferedAsync();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
result.StandardOutput.Trim().Should().Be("Hello Mars!");
|
result.StandardOutput.Trim().Should().Be("Hello Mars!");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,22 +7,22 @@ using FluentAssertions;
|
|||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace CliFx.Tests
|
namespace CliFx.Tests;
|
||||||
{
|
|
||||||
public class ErrorReportingSpecs : SpecsBase
|
|
||||||
{
|
|
||||||
public ErrorReportingSpecs(ITestOutputHelper testOutput)
|
|
||||||
: base(testOutput)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
public class ErrorReportingSpecs : SpecsBase
|
||||||
public async Task Command_can_throw_an_exception_which_exits_with_a_stacktrace()
|
{
|
||||||
{
|
public ErrorReportingSpecs(ITestOutputHelper testOutput)
|
||||||
// Arrange
|
: base(testOutput)
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
{
|
||||||
// language=cs
|
}
|
||||||
@"
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Command_can_throw_an_exception_which_exits_with_a_stacktrace()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
|
// language=cs
|
||||||
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -31,36 +31,36 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdOut.Should().BeEmpty();
|
stdOut.Should().BeEmpty();
|
||||||
stdErr.Should().ContainAllInOrder(
|
stdErr.Should().ContainAllInOrder(
|
||||||
"System.Exception", "Something went wrong",
|
"System.Exception", "Something went wrong",
|
||||||
"at", "CliFx."
|
"at", "CliFx."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Command_can_throw_an_exception_with_an_inner_exception_which_exits_with_a_stacktrace()
|
public async Task Command_can_throw_an_exception_with_an_inner_exception_which_exits_with_a_stacktrace()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -69,37 +69,37 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdOut.Should().BeEmpty();
|
stdOut.Should().BeEmpty();
|
||||||
stdErr.Should().ContainAllInOrder(
|
stdErr.Should().ContainAllInOrder(
|
||||||
"System.Exception", "Something went wrong",
|
"System.Exception", "Something went wrong",
|
||||||
"System.Exception", "Another exception",
|
"System.Exception", "Another exception",
|
||||||
"at", "CliFx."
|
"at", "CliFx."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Command_can_throw_a_special_exception_which_exits_with_specified_code_and_message()
|
public async Task Command_can_throw_a_special_exception_which_exits_with_specified_code_and_message()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -108,33 +108,33 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(69);
|
exitCode.Should().Be(69);
|
||||||
stdOut.Should().BeEmpty();
|
stdOut.Should().BeEmpty();
|
||||||
stdErr.Trim().Should().Be("Something went wrong");
|
stdErr.Trim().Should().Be("Something went wrong");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Command_can_throw_a_special_exception_without_message_which_exits_with_a_stacktrace()
|
public async Task Command_can_throw_a_special_exception_without_message_which_exits_with_a_stacktrace()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -143,36 +143,36 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(69);
|
exitCode.Should().Be(69);
|
||||||
stdOut.Should().BeEmpty();
|
stdOut.Should().BeEmpty();
|
||||||
stdErr.Should().ContainAllInOrder(
|
stdErr.Should().ContainAllInOrder(
|
||||||
"CliFx.Exceptions.CommandException",
|
"CliFx.Exceptions.CommandException",
|
||||||
"at", "CliFx."
|
"at", "CliFx."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Command_can_throw_a_special_exception_which_prints_help_text_before_exiting()
|
public async Task Command_can_throw_a_special_exception_which_prints_help_text_before_exiting()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -181,25 +181,24 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.SetDescription("This will be in help text")
|
.SetDescription("This will be in help text")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(69);
|
exitCode.Should().Be(69);
|
||||||
stdOut.Should().Contain("This will be in help text");
|
stdOut.Should().Contain("This will be in help text");
|
||||||
stdErr.Trim().Should().Be("Something went wrong");
|
stdErr.Trim().Should().Be("Something went wrong");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -7,22 +7,22 @@ using FluentAssertions;
|
|||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace CliFx.Tests
|
namespace CliFx.Tests;
|
||||||
{
|
|
||||||
public class OptionBindingSpecs : SpecsBase
|
|
||||||
{
|
|
||||||
public OptionBindingSpecs(ITestOutputHelper testOutput)
|
|
||||||
: base(testOutput)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
public class OptionBindingSpecs : SpecsBase
|
||||||
public async Task Option_is_bound_from_an_argument_matching_its_name()
|
{
|
||||||
{
|
public OptionBindingSpecs(ITestOutputHelper testOutput)
|
||||||
// Arrange
|
: base(testOutput)
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
{
|
||||||
// language=cs
|
}
|
||||||
@"
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Option_is_bound_from_an_argument_matching_its_name()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
|
// language=cs
|
||||||
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -36,31 +36,31 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"--foo"},
|
new[] {"--foo"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Trim().Should().Be("True");
|
stdOut.Trim().Should().Be("True");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_is_bound_from_an_argument_matching_its_short_name()
|
public async Task Option_is_bound_from_an_argument_matching_its_short_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -74,31 +74,31 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"-f"},
|
new[] {"-f"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Trim().Should().Be("True");
|
stdOut.Trim().Should().Be("True");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_is_bound_from_a_set_of_arguments_matching_its_name()
|
public async Task Option_is_bound_from_a_set_of_arguments_matching_its_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -117,34 +117,34 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"--foo", "one", "--bar", "two"},
|
new[] {"--foo", "one", "--bar", "two"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Should().ConsistOfLines(
|
stdOut.Should().ConsistOfLines(
|
||||||
"Foo = one",
|
"Foo = one",
|
||||||
"Bar = two"
|
"Bar = two"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_is_bound_from_a_set_of_arguments_matching_its_short_name()
|
public async Task Option_is_bound_from_a_set_of_arguments_matching_its_short_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -163,34 +163,34 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"-f", "one", "-b", "two"},
|
new[] {"-f", "one", "-b", "two"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Should().ConsistOfLines(
|
stdOut.Should().ConsistOfLines(
|
||||||
"Foo = one",
|
"Foo = one",
|
||||||
"Bar = two"
|
"Bar = two"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_is_bound_from_a_stack_of_arguments_matching_its_short_name()
|
public async Task Option_is_bound_from_a_stack_of_arguments_matching_its_short_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -209,34 +209,34 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"-fb", "value"},
|
new[] {"-fb", "value"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Should().ConsistOfLines(
|
stdOut.Should().ConsistOfLines(
|
||||||
"Foo = ",
|
"Foo = ",
|
||||||
"Bar = value"
|
"Bar = value"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_of_non_scalar_type_is_bound_from_a_set_of_arguments_matching_its_name()
|
public async Task Option_of_non_scalar_type_is_bound_from_a_set_of_arguments_matching_its_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -252,35 +252,35 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"--foo", "one", "two", "three"},
|
new[] {"--foo", "one", "two", "three"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Should().ConsistOfLines(
|
stdOut.Should().ConsistOfLines(
|
||||||
"one",
|
"one",
|
||||||
"two",
|
"two",
|
||||||
"three"
|
"three"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_of_non_scalar_type_is_bound_from_a_set_of_arguments_matching_its_short_name()
|
public async Task Option_of_non_scalar_type_is_bound_from_a_set_of_arguments_matching_its_short_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -296,35 +296,35 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"-f", "one", "two", "three"},
|
new[] {"-f", "one", "two", "three"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Should().ConsistOfLines(
|
stdOut.Should().ConsistOfLines(
|
||||||
"one",
|
"one",
|
||||||
"two",
|
"two",
|
||||||
"three"
|
"three"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_of_non_scalar_type_is_bound_from_multiple_sets_of_arguments_matching_its_name()
|
public async Task Option_of_non_scalar_type_is_bound_from_multiple_sets_of_arguments_matching_its_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -340,35 +340,35 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"--foo", "one", "--foo", "two", "--foo", "three"},
|
new[] {"--foo", "one", "--foo", "two", "--foo", "three"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Should().ConsistOfLines(
|
stdOut.Should().ConsistOfLines(
|
||||||
"one",
|
"one",
|
||||||
"two",
|
"two",
|
||||||
"three"
|
"three"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_of_non_scalar_type_is_bound_from_multiple_sets_of_arguments_matching_its_short_name()
|
public async Task Option_of_non_scalar_type_is_bound_from_multiple_sets_of_arguments_matching_its_short_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -384,35 +384,35 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"-f", "one", "-f", "two", "-f", "three"},
|
new[] {"-f", "one", "-f", "two", "-f", "three"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Should().ConsistOfLines(
|
stdOut.Should().ConsistOfLines(
|
||||||
"one",
|
"one",
|
||||||
"two",
|
"two",
|
||||||
"three"
|
"three"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_of_non_scalar_type_is_bound_from_multiple_sets_of_arguments_matching_its_name_or_short_name()
|
public async Task Option_of_non_scalar_type_is_bound_from_multiple_sets_of_arguments_matching_its_name_or_short_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -428,35 +428,35 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"--foo", "one", "-f", "two", "--foo", "three"},
|
new[] {"--foo", "one", "-f", "two", "--foo", "three"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Should().ConsistOfLines(
|
stdOut.Should().ConsistOfLines(
|
||||||
"one",
|
"one",
|
||||||
"two",
|
"two",
|
||||||
"three"
|
"three"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_is_not_bound_if_there_are_no_arguments_matching_its_name_or_short_name()
|
public async Task Option_is_not_bound_if_there_are_no_arguments_matching_its_name_or_short_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -475,34 +475,34 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"--foo", "one"},
|
new[] {"--foo", "one"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Should().ConsistOfLines(
|
stdOut.Should().ConsistOfLines(
|
||||||
"Foo = one",
|
"Foo = one",
|
||||||
"Bar = hello"
|
"Bar = hello"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_binding_does_not_consider_a_negative_number_as_an_option_name_or_short_name()
|
public async Task Option_binding_does_not_consider_a_negative_number_as_an_option_name_or_short_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -517,31 +517,31 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"--foo", "-13"},
|
new[] {"--foo", "-13"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Trim().Should().Be("-13");
|
stdOut.Trim().Should().Be("-13");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_binding_fails_if_a_required_option_has_not_been_provided()
|
public async Task Option_binding_fails_if_a_required_option_has_not_been_provided()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -551,31 +551,31 @@ public class Command : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdErr.Should().Contain("Missing required option(s)");
|
stdErr.Should().Contain("Missing required option(s)");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_binding_fails_if_a_required_option_has_been_provided_with_an_empty_value()
|
public async Task Option_binding_fails_if_a_required_option_has_been_provided_with_an_empty_value()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -585,31 +585,31 @@ public class Command : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"--foo"},
|
new[] {"--foo"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdErr.Should().Contain("Missing required option(s)");
|
stdErr.Should().Contain("Missing required option(s)");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_binding_fails_if_a_required_option_of_non_scalar_type_has_not_been_provided_with_at_least_one_value()
|
public async Task Option_binding_fails_if_a_required_option_of_non_scalar_type_has_not_been_provided_with_at_least_one_value()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -619,31 +619,31 @@ public class Command : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"--foo"},
|
new[] {"--foo"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdErr.Should().Contain("Missing required option(s)");
|
stdErr.Should().Contain("Missing required option(s)");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_binding_fails_if_one_of_the_provided_option_names_is_not_recognized()
|
public async Task Option_binding_fails_if_one_of_the_provided_option_names_is_not_recognized()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -653,31 +653,31 @@ public class Command : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"--foo", "one", "--bar", "two"},
|
new[] {"--foo", "one", "--bar", "two"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdErr.Should().Contain("Unrecognized option(s)");
|
stdErr.Should().Contain("Unrecognized option(s)");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_binding_fails_if_an_option_of_scalar_type_has_been_provided_with_multiple_values()
|
public async Task Option_binding_fails_if_an_option_of_scalar_type_has_been_provided_with_multiple_values()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -687,22 +687,21 @@ public class Command : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"--foo", "one", "two", "three"},
|
new[] {"--foo", "one", "two", "three"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdErr.Should().Contain("expects a single argument, but provided with multiple");
|
stdErr.Should().Contain("expects a single argument, but provided with multiple");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,22 +6,22 @@ using FluentAssertions;
|
|||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace CliFx.Tests
|
namespace CliFx.Tests;
|
||||||
{
|
|
||||||
public class ParameterBindingSpecs : SpecsBase
|
|
||||||
{
|
|
||||||
public ParameterBindingSpecs(ITestOutputHelper testOutput)
|
|
||||||
: base(testOutput)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
public class ParameterBindingSpecs : SpecsBase
|
||||||
public async Task Parameter_is_bound_from_an_argument_matching_its_order()
|
{
|
||||||
{
|
public ParameterBindingSpecs(ITestOutputHelper testOutput)
|
||||||
// Arrange
|
: base(testOutput)
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
{
|
||||||
// language=cs
|
}
|
||||||
@"
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Parameter_is_bound_from_an_argument_matching_its_order()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
|
// language=cs
|
||||||
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -40,34 +40,34 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"one", "two"},
|
new[] {"one", "two"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Should().ConsistOfLines(
|
stdOut.Should().ConsistOfLines(
|
||||||
"Foo = one",
|
"Foo = one",
|
||||||
"Bar = two"
|
"Bar = two"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Parameter_of_non_scalar_type_is_bound_from_remaining_non_option_arguments()
|
public async Task Parameter_of_non_scalar_type_is_bound_from_remaining_non_option_arguments()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -95,37 +95,37 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"one", "two", "three", "four", "five", "--boo", "xxx"},
|
new[] {"one", "two", "three", "four", "five", "--boo", "xxx"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Should().ConsistOfLines(
|
stdOut.Should().ConsistOfLines(
|
||||||
"Foo = one",
|
"Foo = one",
|
||||||
"Bar = two",
|
"Bar = two",
|
||||||
"Baz = three",
|
"Baz = three",
|
||||||
"Baz = four",
|
"Baz = four",
|
||||||
"Baz = five"
|
"Baz = five"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Parameter_binding_fails_if_one_of_the_parameters_has_not_been_provided()
|
public async Task Parameter_binding_fails_if_one_of_the_parameters_has_not_been_provided()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -138,31 +138,31 @@ public class Command : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"one"},
|
new[] {"one"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdErr.Should().Contain("Missing parameter(s)");
|
stdErr.Should().Contain("Missing parameter(s)");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Parameter_binding_fails_if_a_parameter_of_non_scalar_type_has_not_been_provided_with_at_least_one_value()
|
public async Task Parameter_binding_fails_if_a_parameter_of_non_scalar_type_has_not_been_provided_with_at_least_one_value()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -175,31 +175,31 @@ public class Command : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"one"},
|
new[] {"one"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdErr.Should().Contain("Missing parameter(s)");
|
stdErr.Should().Contain("Missing parameter(s)");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Parameter_binding_fails_if_one_of_the_provided_parameters_is_unexpected()
|
public async Task Parameter_binding_fails_if_one_of_the_provided_parameters_is_unexpected()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -212,22 +212,21 @@ public class Command : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"one", "two", "three"},
|
new[] {"one", "two", "three"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdErr.Should().Contain("Unexpected parameter(s)");
|
stdErr.Should().Contain("Unexpected parameter(s)");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,22 +6,22 @@ using FluentAssertions;
|
|||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace CliFx.Tests
|
namespace CliFx.Tests;
|
||||||
{
|
|
||||||
public class RoutingSpecs : SpecsBase
|
|
||||||
{
|
|
||||||
public RoutingSpecs(ITestOutputHelper testOutput)
|
|
||||||
: base(testOutput)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
public class RoutingSpecs : SpecsBase
|
||||||
public async Task Default_command_is_executed_if_provided_arguments_do_not_match_any_named_command()
|
{
|
||||||
{
|
public RoutingSpecs(ITestOutputHelper testOutput)
|
||||||
// Arrange
|
: base(testOutput)
|
||||||
var commandTypes = DynamicCommandBuilder.CompileMany(
|
{
|
||||||
// language=cs
|
}
|
||||||
@"
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Default_command_is_executed_if_provided_arguments_do_not_match_any_named_command()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandTypes = DynamicCommandBuilder.CompileMany(
|
||||||
|
// language=cs
|
||||||
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class DefaultCommand : ICommand
|
public class DefaultCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -53,31 +53,31 @@ public class NamedChildCommand : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommands(commandTypes)
|
.AddCommands(commandTypes)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Trim().Should().Be("default");
|
stdOut.Trim().Should().Be("default");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Specific_named_command_is_executed_if_provided_arguments_match_its_name()
|
public async Task Specific_named_command_is_executed_if_provided_arguments_match_its_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandTypes = DynamicCommandBuilder.CompileMany(
|
var commandTypes = DynamicCommandBuilder.CompileMany(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class DefaultCommand : ICommand
|
public class DefaultCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -109,31 +109,31 @@ public class NamedChildCommand : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommands(commandTypes)
|
.AddCommands(commandTypes)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"cmd"},
|
new[] {"cmd"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Trim().Should().Be("cmd");
|
stdOut.Trim().Should().Be("cmd");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Specific_named_child_command_is_executed_if_provided_arguments_match_its_name()
|
public async Task Specific_named_child_command_is_executed_if_provided_arguments_match_its_name()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandTypes = DynamicCommandBuilder.CompileMany(
|
var commandTypes = DynamicCommandBuilder.CompileMany(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class DefaultCommand : ICommand
|
public class DefaultCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -165,22 +165,21 @@ public class NamedChildCommand : ICommand
|
|||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommands(commandTypes)
|
.AddCommands(commandTypes)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
new[] {"cmd", "child"},
|
new[] {"cmd", "child"},
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Trim().Should().Be("cmd child");
|
stdOut.Trim().Should().Be("cmd child");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,21 +3,20 @@ using CliFx.Infrastructure;
|
|||||||
using CliFx.Tests.Utils.Extensions;
|
using CliFx.Tests.Utils.Extensions;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace CliFx.Tests
|
namespace CliFx.Tests;
|
||||||
|
|
||||||
|
public abstract class SpecsBase : IDisposable
|
||||||
{
|
{
|
||||||
public abstract class SpecsBase : IDisposable
|
public ITestOutputHelper TestOutput { get; }
|
||||||
|
|
||||||
|
public FakeInMemoryConsole FakeConsole { get; } = new();
|
||||||
|
|
||||||
|
protected SpecsBase(ITestOutputHelper testOutput) =>
|
||||||
|
TestOutput = testOutput;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
{
|
{
|
||||||
public ITestOutputHelper TestOutput { get; }
|
FakeConsole.DumpToTestOutput(TestOutput);
|
||||||
|
FakeConsole.Dispose();
|
||||||
public FakeInMemoryConsole FakeConsole { get; } = new();
|
|
||||||
|
|
||||||
protected SpecsBase(ITestOutputHelper testOutput) =>
|
|
||||||
TestOutput = testOutput;
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
FakeConsole.DumpToTestOutput(TestOutput);
|
|
||||||
FakeConsole.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,22 +7,22 @@ using FluentAssertions;
|
|||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace CliFx.Tests
|
namespace CliFx.Tests;
|
||||||
{
|
|
||||||
public class TypeActivationSpecs : SpecsBase
|
|
||||||
{
|
|
||||||
public TypeActivationSpecs(ITestOutputHelper testOutput)
|
|
||||||
: base(testOutput)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
public class TypeActivationSpecs : SpecsBase
|
||||||
public async Task Default_type_activator_can_initialize_a_type_if_it_has_a_parameterless_constructor()
|
{
|
||||||
{
|
public TypeActivationSpecs(ITestOutputHelper testOutput)
|
||||||
// Arrange
|
: base(testOutput)
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
{
|
||||||
// language=cs
|
}
|
||||||
@"
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Default_type_activator_can_initialize_a_type_if_it_has_a_parameterless_constructor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
|
// language=cs
|
||||||
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -33,32 +33,32 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.UseTypeActivator(new DefaultTypeActivator())
|
.UseTypeActivator(new DefaultTypeActivator())
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Trim().Should().Be("foo");
|
stdOut.Trim().Should().Be("foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Default_type_activator_fails_if_the_type_does_not_have_a_parameterless_constructor()
|
public async Task Default_type_activator_fails_if_the_type_does_not_have_a_parameterless_constructor()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -67,32 +67,32 @@ public class Command : ICommand
|
|||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.UseTypeActivator(new DefaultTypeActivator())
|
.UseTypeActivator(new DefaultTypeActivator())
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdErr.Should().Contain("Failed to create an instance of type");
|
stdErr.Should().Contain("Failed to create an instance of type");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Delegate_type_activator_can_initialize_a_type_using_a_custom_function()
|
public async Task Delegate_type_activator_can_initialize_a_type_using_a_custom_function()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -107,32 +107,32 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.UseTypeActivator(type => Activator.CreateInstance(type, "hello world")!)
|
.UseTypeActivator(type => Activator.CreateInstance(type, "hello world")!)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdOut = FakeConsole.ReadOutputString();
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().Be(0);
|
exitCode.Should().Be(0);
|
||||||
stdOut.Trim().Should().Be("hello world");
|
stdOut.Trim().Should().Be("hello world");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Delegate_type_activator_fails_if_the_underlying_function_returns_null()
|
public async Task Delegate_type_activator_fails_if_the_underlying_function_returns_null()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
// language=cs
|
// language=cs
|
||||||
@"
|
@"
|
||||||
[Command]
|
[Command]
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -143,23 +143,22 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(commandType)
|
.AddCommand(commandType)
|
||||||
.UseConsole(FakeConsole)
|
.UseConsole(FakeConsole)
|
||||||
.UseTypeActivator(_ => null!)
|
.UseTypeActivator(_ => null!)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exitCode = await application.RunAsync(
|
var exitCode = await application.RunAsync(
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var stdErr = FakeConsole.ReadErrorString();
|
var stdErr = FakeConsole.ReadErrorString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdErr.Should().Contain("Failed to create an instance of type");
|
stdErr.Should().Contain("Failed to create an instance of type");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,129 +8,128 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp;
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
using Microsoft.CodeAnalysis.Text;
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
namespace CliFx.Tests.Utils
|
namespace CliFx.Tests.Utils;
|
||||||
|
|
||||||
|
// This class uses Roslyn to compile commands dynamically.
|
||||||
|
//
|
||||||
|
// It allows us to collocate commands with tests more
|
||||||
|
// easily, which helps a lot when reasoning about them.
|
||||||
|
// Unfortunately, this comes at a cost of static typing,
|
||||||
|
// but this is still a worthwhile trade off.
|
||||||
|
//
|
||||||
|
// Maybe one day C# will allow declaring classes inside
|
||||||
|
// methods and doing this will no longer be necessary.
|
||||||
|
// Language proposal: https://github.com/dotnet/csharplang/discussions/130
|
||||||
|
internal static class DynamicCommandBuilder
|
||||||
{
|
{
|
||||||
// This class uses Roslyn to compile commands dynamically.
|
public static IReadOnlyList<Type> CompileMany(string sourceCode)
|
||||||
//
|
|
||||||
// It allows us to collocate commands with tests more
|
|
||||||
// easily, which helps a lot when reasoning about them.
|
|
||||||
// Unfortunately, this comes at a cost of static typing,
|
|
||||||
// but this is still a worthwhile trade off.
|
|
||||||
//
|
|
||||||
// Maybe one day C# will allow declaring classes inside
|
|
||||||
// methods and doing this will no longer be necessary.
|
|
||||||
// Language proposal: https://github.com/dotnet/csharplang/discussions/130
|
|
||||||
internal static class DynamicCommandBuilder
|
|
||||||
{
|
{
|
||||||
public static IReadOnlyList<Type> CompileMany(string sourceCode)
|
// Get default system namespaces
|
||||||
|
var defaultSystemNamespaces = new[]
|
||||||
{
|
{
|
||||||
// Get default system namespaces
|
"System",
|
||||||
var defaultSystemNamespaces = new[]
|
"System.Collections",
|
||||||
{
|
"System.Collections.Generic",
|
||||||
"System",
|
"System.Linq",
|
||||||
"System.Collections",
|
"System.Threading.Tasks",
|
||||||
"System.Collections.Generic",
|
"System.Globalization"
|
||||||
"System.Linq",
|
};
|
||||||
"System.Threading.Tasks",
|
|
||||||
"System.Globalization"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get default CliFx namespaces
|
// Get default CliFx namespaces
|
||||||
var defaultCliFxNamespaces = typeof(ICommand)
|
var defaultCliFxNamespaces = typeof(ICommand)
|
||||||
.Assembly
|
.Assembly
|
||||||
.GetTypes()
|
.GetTypes()
|
||||||
.Where(t => t.IsPublic)
|
.Where(t => t.IsPublic)
|
||||||
.Select(t => t.Namespace)
|
.Select(t => t.Namespace)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
// Append default imports to the source code
|
// Append default imports to the source code
|
||||||
var sourceCodeWithUsings =
|
var sourceCodeWithUsings =
|
||||||
string.Join(Environment.NewLine, defaultSystemNamespaces.Select(n => $"using {n};")) +
|
string.Join(Environment.NewLine, defaultSystemNamespaces.Select(n => $"using {n};")) +
|
||||||
string.Join(Environment.NewLine, defaultCliFxNamespaces.Select(n => $"using {n};")) +
|
string.Join(Environment.NewLine, defaultCliFxNamespaces.Select(n => $"using {n};")) +
|
||||||
|
Environment.NewLine +
|
||||||
|
sourceCode;
|
||||||
|
|
||||||
|
// Parse the source code
|
||||||
|
var ast = SyntaxFactory.ParseSyntaxTree(
|
||||||
|
SourceText.From(sourceCodeWithUsings),
|
||||||
|
CSharpParseOptions.Default
|
||||||
|
);
|
||||||
|
|
||||||
|
// Compile the code to IL
|
||||||
|
var compilation = CSharpCompilation.Create(
|
||||||
|
"CliFxTests_DynamicAssembly_" + Guid.NewGuid(),
|
||||||
|
new[] {ast},
|
||||||
|
ReferenceAssemblies.Net50
|
||||||
|
.Append(MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location))
|
||||||
|
.Append(MetadataReference.CreateFromFile(typeof(DynamicCommandBuilder).Assembly.Location)),
|
||||||
|
// DLL to avoid having to define the Main() method
|
||||||
|
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
|
||||||
|
);
|
||||||
|
|
||||||
|
var compilationErrors = compilation
|
||||||
|
.GetDiagnostics()
|
||||||
|
.Where(d => d.Severity >= DiagnosticSeverity.Error)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (compilationErrors.Any())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Failed to compile code." +
|
||||||
Environment.NewLine +
|
Environment.NewLine +
|
||||||
sourceCode;
|
string.Join(Environment.NewLine, compilationErrors.Select(e => e.ToString()))
|
||||||
|
|
||||||
// Parse the source code
|
|
||||||
var ast = SyntaxFactory.ParseSyntaxTree(
|
|
||||||
SourceText.From(sourceCodeWithUsings),
|
|
||||||
CSharpParseOptions.Default
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Compile the code to IL
|
|
||||||
var compilation = CSharpCompilation.Create(
|
|
||||||
"CliFxTests_DynamicAssembly_" + Guid.NewGuid(),
|
|
||||||
new[] {ast},
|
|
||||||
ReferenceAssemblies.Net50
|
|
||||||
.Append(MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location))
|
|
||||||
.Append(MetadataReference.CreateFromFile(typeof(DynamicCommandBuilder).Assembly.Location)),
|
|
||||||
// DLL to avoid having to define the Main() method
|
|
||||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
|
|
||||||
);
|
|
||||||
|
|
||||||
var compilationErrors = compilation
|
|
||||||
.GetDiagnostics()
|
|
||||||
.Where(d => d.Severity >= DiagnosticSeverity.Error)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
if (compilationErrors.Any())
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"Failed to compile code." +
|
|
||||||
Environment.NewLine +
|
|
||||||
string.Join(Environment.NewLine, compilationErrors.Select(e => e.ToString()))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit the code to an in-memory buffer
|
|
||||||
using var buffer = new MemoryStream();
|
|
||||||
var emit = compilation.Emit(buffer);
|
|
||||||
|
|
||||||
var emitErrors = emit
|
|
||||||
.Diagnostics
|
|
||||||
.Where(d => d.Severity >= DiagnosticSeverity.Error)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
if (emitErrors.Any())
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"Failed to emit code." +
|
|
||||||
Environment.NewLine +
|
|
||||||
string.Join(Environment.NewLine, emitErrors.Select(e => e.ToString()))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the generated assembly
|
|
||||||
var generatedAssembly = Assembly.Load(buffer.ToArray());
|
|
||||||
|
|
||||||
// Return all defined commands
|
|
||||||
var commandTypes = generatedAssembly
|
|
||||||
.GetTypes()
|
|
||||||
.Where(t => t.IsAssignableTo(typeof(ICommand)))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
if (commandTypes.Length <= 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"There are no command definitions in the provide source code."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return commandTypes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Type Compile(string sourceCode)
|
// Emit the code to an in-memory buffer
|
||||||
|
using var buffer = new MemoryStream();
|
||||||
|
var emit = compilation.Emit(buffer);
|
||||||
|
|
||||||
|
var emitErrors = emit
|
||||||
|
.Diagnostics
|
||||||
|
.Where(d => d.Severity >= DiagnosticSeverity.Error)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (emitErrors.Any())
|
||||||
{
|
{
|
||||||
var commandTypes = CompileMany(sourceCode);
|
throw new InvalidOperationException(
|
||||||
|
"Failed to emit code." +
|
||||||
if (commandTypes.Count > 1)
|
Environment.NewLine +
|
||||||
{
|
string.Join(Environment.NewLine, emitErrors.Select(e => e.ToString()))
|
||||||
throw new InvalidOperationException(
|
);
|
||||||
"There are more than one command definitions in the provide source code."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return commandTypes.Single();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load the generated assembly
|
||||||
|
var generatedAssembly = Assembly.Load(buffer.ToArray());
|
||||||
|
|
||||||
|
// Return all defined commands
|
||||||
|
var commandTypes = generatedAssembly
|
||||||
|
.GetTypes()
|
||||||
|
.Where(t => t.IsAssignableTo(typeof(ICommand)) && !t.IsAbstract)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (commandTypes.Length <= 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"There are no command definitions in the provided source code."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return commandTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type Compile(string sourceCode)
|
||||||
|
{
|
||||||
|
var commandTypes = CompileMany(sourceCode);
|
||||||
|
|
||||||
|
if (commandTypes.Count > 1)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"There are more than one command definitions in the provided source code."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return commandTypes.Single();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,54 +1,51 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using FluentAssertions.Collections;
|
|
||||||
using FluentAssertions.Execution;
|
using FluentAssertions.Execution;
|
||||||
using FluentAssertions.Primitives;
|
using FluentAssertions.Primitives;
|
||||||
|
|
||||||
namespace CliFx.Tests.Utils.Extensions
|
namespace CliFx.Tests.Utils.Extensions;
|
||||||
|
|
||||||
|
internal static class AssertionExtensions
|
||||||
{
|
{
|
||||||
internal static class AssertionExtensions
|
public static void ConsistOfLines(
|
||||||
|
this StringAssertions assertions,
|
||||||
|
IEnumerable<string> lines)
|
||||||
{
|
{
|
||||||
public static AndConstraint<StringCollectionAssertions> ConsistOfLines(
|
var actualLines = assertions.Subject.Split(new[] {'\n', '\r'}, StringSplitOptions.RemoveEmptyEntries);
|
||||||
this StringAssertions assertions,
|
actualLines.Should().Equal(lines);
|
||||||
IEnumerable<string> lines)
|
}
|
||||||
|
|
||||||
|
public static void ConsistOfLines(
|
||||||
|
this StringAssertions assertions,
|
||||||
|
params string[] lines) =>
|
||||||
|
assertions.ConsistOfLines((IEnumerable<string>) lines);
|
||||||
|
|
||||||
|
public static AndConstraint<StringAssertions> ContainAllInOrder(
|
||||||
|
this StringAssertions assertions,
|
||||||
|
IEnumerable<string> values)
|
||||||
|
{
|
||||||
|
var lastIndex = 0;
|
||||||
|
|
||||||
|
foreach (var value in values)
|
||||||
{
|
{
|
||||||
var actualLines = assertions.Subject.Split(new[] {'\n', '\r'}, StringSplitOptions.RemoveEmptyEntries);
|
var index = assertions.Subject.IndexOf(value, lastIndex, StringComparison.Ordinal);
|
||||||
|
|
||||||
return actualLines.Should().Equal(lines);
|
if (index < 0)
|
||||||
}
|
|
||||||
|
|
||||||
public static AndConstraint<StringCollectionAssertions> ConsistOfLines(
|
|
||||||
this StringAssertions assertions,
|
|
||||||
params string[] lines) =>
|
|
||||||
assertions.ConsistOfLines((IEnumerable<string>) lines);
|
|
||||||
|
|
||||||
public static AndConstraint<StringAssertions> ContainAllInOrder(
|
|
||||||
this StringAssertions assertions,
|
|
||||||
IEnumerable<string> values)
|
|
||||||
{
|
|
||||||
var lastIndex = 0;
|
|
||||||
|
|
||||||
foreach (var value in values)
|
|
||||||
{
|
{
|
||||||
var index = assertions.Subject.IndexOf(value, lastIndex, StringComparison.Ordinal);
|
Execute.Assertion.FailWith(
|
||||||
|
$"Expected string '{assertions.Subject}' to contain '{value}' after position {lastIndex}."
|
||||||
if (index < 0)
|
);
|
||||||
{
|
|
||||||
Execute.Assertion.FailWith(
|
|
||||||
$"Expected string '{assertions.Subject}' to contain '{value}' after position {lastIndex}."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastIndex = index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new(assertions);
|
lastIndex = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AndConstraint<StringAssertions> ContainAllInOrder(
|
return new(assertions);
|
||||||
this StringAssertions assertions,
|
|
||||||
params string[] values) =>
|
|
||||||
assertions.ContainAllInOrder((IEnumerable<string>) values);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static AndConstraint<StringAssertions> ContainAllInOrder(
|
||||||
|
this StringAssertions assertions,
|
||||||
|
params string[] values) =>
|
||||||
|
assertions.ContainAllInOrder((IEnumerable<string>) values);
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace CliFx.Tests.Utils.Extensions
|
namespace CliFx.Tests.Utils.Extensions;
|
||||||
{
|
|
||||||
internal static class ConsoleExtensions
|
|
||||||
{
|
|
||||||
public static void DumpToTestOutput(this FakeInMemoryConsole console, ITestOutputHelper testOutputHelper)
|
|
||||||
{
|
|
||||||
testOutputHelper.WriteLine("[*] Captured standard output:");
|
|
||||||
testOutputHelper.WriteLine(console.ReadOutputString());
|
|
||||||
|
|
||||||
testOutputHelper.WriteLine("[*] Captured standard error:");
|
internal static class ConsoleExtensions
|
||||||
testOutputHelper.WriteLine(console.ReadErrorString());
|
{
|
||||||
}
|
public static void DumpToTestOutput(this FakeInMemoryConsole console, ITestOutputHelper testOutputHelper)
|
||||||
|
{
|
||||||
|
testOutputHelper.WriteLine("[*] Captured standard output:");
|
||||||
|
testOutputHelper.WriteLine(console.ReadOutputString());
|
||||||
|
|
||||||
|
testOutputHelper.WriteLine("[*] Captured standard error:");
|
||||||
|
testOutputHelper.WriteLine(console.ReadErrorString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,10 @@
|
|||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
|
|
||||||
namespace CliFx.Tests.Utils
|
namespace CliFx.Tests.Utils;
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
public class NoOpCommand : ICommand
|
||||||
{
|
{
|
||||||
[Command]
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
public class NoOpCommand : ICommand
|
|
||||||
{
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,39 +1,38 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace CliFx
|
namespace CliFx;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration of an application.
|
||||||
|
/// </summary>
|
||||||
|
public class ApplicationConfiguration
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configuration of an application.
|
/// Command types defined in this application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ApplicationConfiguration
|
public IReadOnlyList<Type> CommandTypes { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether debug mode is allowed in this application.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDebugModeAllowed { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether preview mode is allowed in this application.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsPreviewModeAllowed { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="ApplicationConfiguration"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ApplicationConfiguration(
|
||||||
|
IReadOnlyList<Type> commandTypes,
|
||||||
|
bool isDebugModeAllowed,
|
||||||
|
bool isPreviewModeAllowed)
|
||||||
{
|
{
|
||||||
/// <summary>
|
CommandTypes = commandTypes;
|
||||||
/// Command types defined in this application.
|
IsDebugModeAllowed = isDebugModeAllowed;
|
||||||
/// </summary>
|
IsPreviewModeAllowed = isPreviewModeAllowed;
|
||||||
public IReadOnlyList<Type> CommandTypes { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether debug mode is allowed in this application.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsDebugModeAllowed { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether preview mode is allowed in this application.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsPreviewModeAllowed { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="ApplicationConfiguration"/>.
|
|
||||||
/// </summary>
|
|
||||||
public ApplicationConfiguration(
|
|
||||||
IReadOnlyList<Type> commandTypes,
|
|
||||||
bool isDebugModeAllowed,
|
|
||||||
bool isPreviewModeAllowed)
|
|
||||||
{
|
|
||||||
CommandTypes = commandTypes;
|
|
||||||
IsDebugModeAllowed = isDebugModeAllowed;
|
|
||||||
IsPreviewModeAllowed = isPreviewModeAllowed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,43 +1,42 @@
|
|||||||
namespace CliFx
|
namespace CliFx;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Metadata associated with an application.
|
||||||
|
/// </summary>
|
||||||
|
public class ApplicationMetadata
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Metadata associated with an application.
|
/// Application title.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ApplicationMetadata
|
public string Title { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Application executable name.
|
||||||
|
/// </summary>
|
||||||
|
public string ExecutableName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Application version.
|
||||||
|
/// </summary>
|
||||||
|
public string Version { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Application description.
|
||||||
|
/// </summary>
|
||||||
|
public string? Description { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="ApplicationMetadata"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ApplicationMetadata(
|
||||||
|
string title,
|
||||||
|
string executableName,
|
||||||
|
string version,
|
||||||
|
string? description)
|
||||||
{
|
{
|
||||||
/// <summary>
|
Title = title;
|
||||||
/// Application title.
|
ExecutableName = executableName;
|
||||||
/// </summary>
|
Version = version;
|
||||||
public string Title { get; }
|
Description = description;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Application executable name.
|
|
||||||
/// </summary>
|
|
||||||
public string ExecutableName { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Application version.
|
|
||||||
/// </summary>
|
|
||||||
public string Version { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Application description.
|
|
||||||
/// </summary>
|
|
||||||
public string? Description { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="ApplicationMetadata"/>.
|
|
||||||
/// </summary>
|
|
||||||
public ApplicationMetadata(
|
|
||||||
string title,
|
|
||||||
string executableName,
|
|
||||||
string version,
|
|
||||||
string? description)
|
|
||||||
{
|
|
||||||
Title = title;
|
|
||||||
ExecutableName = executableName;
|
|
||||||
Version = version;
|
|
||||||
Description = description;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,43 +1,42 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace CliFx.Attributes
|
namespace CliFx.Attributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Annotates a type that defines a command.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||||
|
public sealed class CommandAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Annotates a type that defines a command.
|
/// Command's name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
/// <remarks>
|
||||||
public sealed class CommandAttribute : Attribute
|
/// Command can have no name, in which case it's treated as the default command.
|
||||||
|
///
|
||||||
|
/// All commands registered in an application must have unique names (comparison IS NOT case-sensitive).
|
||||||
|
/// Only one command without a name is allowed in an application.
|
||||||
|
/// </remarks>
|
||||||
|
public string? Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Command description.
|
||||||
|
/// This is shown to the user in the help text.
|
||||||
|
/// </summary>
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
public CommandAttribute(string name)
|
||||||
{
|
{
|
||||||
/// <summary>
|
Name = name;
|
||||||
/// Command's name.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Command can have no name, in which case it's treated as the default command.
|
|
||||||
///
|
|
||||||
/// All commands registered in an application must have unique names (comparison IS NOT case-sensitive).
|
|
||||||
/// Only one command without a name is allowed in an application.
|
|
||||||
/// </remarks>
|
|
||||||
public string? Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command description.
|
/// Initializes an instance of <see cref="CommandAttribute"/>.
|
||||||
/// This is shown to the user in the help text.
|
/// </summary>
|
||||||
/// </summary>
|
public CommandAttribute()
|
||||||
public string? Description { get; set; }
|
{
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="CommandAttribute"/>.
|
|
||||||
/// </summary>
|
|
||||||
public CommandAttribute(string name)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="CommandAttribute"/>.
|
|
||||||
/// </summary>
|
|
||||||
public CommandAttribute()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,100 +1,99 @@
|
|||||||
using System;
|
using System;
|
||||||
using CliFx.Extensibility;
|
using CliFx.Extensibility;
|
||||||
|
|
||||||
namespace CliFx.Attributes
|
namespace CliFx.Attributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Annotates a property that defines a command option.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public sealed class CommandOptionAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Annotates a property that defines a command option.
|
/// Option name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Property)]
|
/// <remarks>
|
||||||
public sealed class CommandOptionAttribute : Attribute
|
/// Must contain at least two characters and start with a letter.
|
||||||
|
/// Either <see cref="Name"/> or <see cref="ShortName"/> must be set.
|
||||||
|
/// All options in a command must have unique names (comparison IS NOT case-sensitive).
|
||||||
|
/// </remarks>
|
||||||
|
public string? Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Option short name.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Either <see cref="Name"/> or <see cref="ShortName"/> must be set.
|
||||||
|
/// All options in a command must have unique short names (comparison IS case-sensitive).
|
||||||
|
/// </remarks>
|
||||||
|
public char? ShortName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this option is required.
|
||||||
|
/// If an option is required, the user will get an error if they don't set it.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRequired { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Environment variable whose value will be used as a fallback if the option
|
||||||
|
/// has not been explicitly set through command line arguments.
|
||||||
|
/// </summary>
|
||||||
|
public string? EnvironmentVariable { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Option description.
|
||||||
|
/// This is shown to the user in the help text.
|
||||||
|
/// </summary>
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom converter used for mapping the raw command line argument into
|
||||||
|
/// a value expected by the underlying property.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Converter must derive from <see cref="BindingConverter{T}"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public Type? Converter { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom validators used for verifying the value of the underlying
|
||||||
|
/// property, after it has been bound.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Validators must derive from <see cref="BindingValidator{T}"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public Type[] Validators { get; set; } = Array.Empty<Type>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
private CommandOptionAttribute(string? name, char? shortName)
|
||||||
{
|
{
|
||||||
/// <summary>
|
Name = name;
|
||||||
/// Option name.
|
ShortName = shortName;
|
||||||
/// </summary>
|
}
|
||||||
/// <remarks>
|
|
||||||
/// Must contain at least two characters and start with a letter.
|
|
||||||
/// Either <see cref="Name"/> or <see cref="ShortName"/> must be set.
|
|
||||||
/// All options in a command must have unique names (comparison IS NOT case-sensitive).
|
|
||||||
/// </remarks>
|
|
||||||
public string? Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Option short name.
|
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
public CommandOptionAttribute(string name, char shortName)
|
||||||
/// Either <see cref="Name"/> or <see cref="ShortName"/> must be set.
|
: this(name, (char?) shortName)
|
||||||
/// All options in a command must have unique short names (comparison IS case-sensitive).
|
{
|
||||||
/// </remarks>
|
}
|
||||||
public char? ShortName { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this option is required.
|
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
||||||
/// If an option is required, the user will get an error if they don't set it.
|
/// </summary>
|
||||||
/// </summary>
|
public CommandOptionAttribute(string name)
|
||||||
public bool IsRequired { get; set; }
|
: this(name, null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Environment variable whose value will be used as a fallback if the option
|
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
||||||
/// has not been explicitly set through command line arguments.
|
/// </summary>
|
||||||
/// </summary>
|
public CommandOptionAttribute(char shortName)
|
||||||
public string? EnvironmentVariable { get; set; }
|
: this(null, (char?) shortName)
|
||||||
|
{
|
||||||
/// <summary>
|
|
||||||
/// Option description.
|
|
||||||
/// This is shown to the user in the help text.
|
|
||||||
/// </summary>
|
|
||||||
public string? Description { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Custom converter used for mapping the raw command line argument into
|
|
||||||
/// a value expected by the underlying property.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Converter must derive from <see cref="BindingConverter{T}"/>.
|
|
||||||
/// </remarks>
|
|
||||||
public Type? Converter { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Custom validators used for verifying the value of the underlying
|
|
||||||
/// property, after it has been bound.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Validators must derive from <see cref="BindingValidator{T}"/>.
|
|
||||||
/// </remarks>
|
|
||||||
public Type[] Validators { get; set; } = Array.Empty<Type>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
|
||||||
/// </summary>
|
|
||||||
private CommandOptionAttribute(string? name, char? shortName)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
ShortName = shortName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
|
||||||
/// </summary>
|
|
||||||
public CommandOptionAttribute(string name, char shortName)
|
|
||||||
: this(name, (char?) shortName)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
|
||||||
/// </summary>
|
|
||||||
public CommandOptionAttribute(string name)
|
|
||||||
: this(name, null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
|
||||||
/// </summary>
|
|
||||||
public CommandOptionAttribute(char shortName)
|
|
||||||
: this(null, (char?) shortName)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,67 +1,66 @@
|
|||||||
using System;
|
using System;
|
||||||
using CliFx.Extensibility;
|
using CliFx.Extensibility;
|
||||||
|
|
||||||
namespace CliFx.Attributes
|
namespace CliFx.Attributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Annotates a property that defines a command parameter.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public sealed class CommandParameterAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Annotates a property that defines a command parameter.
|
/// Parameter order.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Property)]
|
/// <remarks>
|
||||||
public sealed class CommandParameterAttribute : Attribute
|
/// Higher order means the parameter appears later, lower order means
|
||||||
|
/// it appears earlier.
|
||||||
|
///
|
||||||
|
/// All parameters in a command must have unique order.
|
||||||
|
///
|
||||||
|
/// Parameter whose type is a non-scalar (e.g. array), must always be the last in order.
|
||||||
|
/// Only one non-scalar parameter is allowed in a command.
|
||||||
|
/// </remarks>
|
||||||
|
public int Order { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parameter name.
|
||||||
|
/// This is shown to the user in the help text.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If this isn't specified, parameter name is inferred from the property name.
|
||||||
|
/// </remarks>
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parameter description.
|
||||||
|
/// This is shown to the user in the help text.
|
||||||
|
/// </summary>
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom converter used for mapping the raw command line argument into
|
||||||
|
/// a value expected by the underlying property.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Converter must derive from <see cref="BindingConverter{T}"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public Type? Converter { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom validators used for verifying the value of the underlying
|
||||||
|
/// property, after it has been bound.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Validators must derive from <see cref="BindingValidator{T}"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public Type[] Validators { get; set; } = Array.Empty<Type>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandParameterAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
public CommandParameterAttribute(int order)
|
||||||
{
|
{
|
||||||
/// <summary>
|
Order = order;
|
||||||
/// Parameter order.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Higher order means the parameter appears later, lower order means
|
|
||||||
/// it appears earlier.
|
|
||||||
///
|
|
||||||
/// All parameters in a command must have unique order.
|
|
||||||
///
|
|
||||||
/// Parameter whose type is a non-scalar (e.g. array), must always be the last in order.
|
|
||||||
/// Only one non-scalar parameter is allowed in a command.
|
|
||||||
/// </remarks>
|
|
||||||
public int Order { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parameter name.
|
|
||||||
/// This is shown to the user in the help text.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// If this isn't specified, parameter name is inferred from the property name.
|
|
||||||
/// </remarks>
|
|
||||||
public string? Name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parameter description.
|
|
||||||
/// This is shown to the user in the help text.
|
|
||||||
/// </summary>
|
|
||||||
public string? Description { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Custom converter used for mapping the raw command line argument into
|
|
||||||
/// a value expected by the underlying property.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Converter must derive from <see cref="BindingConverter{T}"/>.
|
|
||||||
/// </remarks>
|
|
||||||
public Type? Converter { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Custom validators used for verifying the value of the underlying
|
|
||||||
/// property, after it has been bound.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Validators must derive from <see cref="BindingValidator{T}"/>.
|
|
||||||
/// </remarks>
|
|
||||||
public Type[] Validators { get; set; } = Array.Empty<Type>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="CommandParameterAttribute"/>.
|
|
||||||
/// </summary>
|
|
||||||
public CommandParameterAttribute(int order)
|
|
||||||
{
|
|
||||||
Order = order;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,231 +11,230 @@ using CliFx.Schema;
|
|||||||
using CliFx.Utils;
|
using CliFx.Utils;
|
||||||
using CliFx.Utils.Extensions;
|
using CliFx.Utils.Extensions;
|
||||||
|
|
||||||
namespace CliFx
|
namespace CliFx;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Command line application facade.
|
||||||
|
/// </summary>
|
||||||
|
public class CliApplication
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command line application facade.
|
/// Application metadata.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CliApplication
|
public ApplicationMetadata Metadata { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Application configuration.
|
||||||
|
/// </summary>
|
||||||
|
public ApplicationConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
private readonly IConsole _console;
|
||||||
|
private readonly ITypeActivator _typeActivator;
|
||||||
|
|
||||||
|
private readonly CommandBinder _commandBinder;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CliApplication"/>.
|
||||||
|
/// </summary>
|
||||||
|
public CliApplication(
|
||||||
|
ApplicationMetadata metadata,
|
||||||
|
ApplicationConfiguration configuration,
|
||||||
|
IConsole console,
|
||||||
|
ITypeActivator typeActivator)
|
||||||
{
|
{
|
||||||
/// <summary>
|
Metadata = metadata;
|
||||||
/// Application metadata.
|
Configuration = configuration;
|
||||||
/// </summary>
|
_console = console;
|
||||||
public ApplicationMetadata Metadata { get; }
|
_typeActivator = typeActivator;
|
||||||
|
|
||||||
/// <summary>
|
_commandBinder = new CommandBinder(typeActivator);
|
||||||
/// Application configuration.
|
}
|
||||||
/// </summary>
|
|
||||||
public ApplicationConfiguration Configuration { get; }
|
|
||||||
|
|
||||||
private readonly IConsole _console;
|
private bool IsDebugModeEnabled(CommandInput commandInput) =>
|
||||||
private readonly ITypeActivator _typeActivator;
|
Configuration.IsDebugModeAllowed && commandInput.IsDebugDirectiveSpecified;
|
||||||
|
|
||||||
private readonly CommandBinder _commandBinder;
|
private bool IsPreviewModeEnabled(CommandInput commandInput) =>
|
||||||
|
Configuration.IsPreviewModeAllowed && commandInput.IsPreviewDirectiveSpecified;
|
||||||
|
|
||||||
/// <summary>
|
private bool ShouldShowHelpText(CommandSchema commandSchema, CommandInput commandInput) =>
|
||||||
/// Initializes an instance of <see cref="CliApplication"/>.
|
commandSchema.IsHelpOptionAvailable && commandInput.IsHelpOptionSpecified ||
|
||||||
/// </summary>
|
// Show help text also in case the fallback default command is
|
||||||
public CliApplication(
|
// executed without any arguments.
|
||||||
ApplicationMetadata metadata,
|
commandSchema == FallbackDefaultCommand.Schema &&
|
||||||
ApplicationConfiguration configuration,
|
string.IsNullOrWhiteSpace(commandInput.CommandName) &&
|
||||||
IConsole console,
|
!commandInput.Parameters.Any() &&
|
||||||
ITypeActivator typeActivator)
|
!commandInput.Options.Any();
|
||||||
|
|
||||||
|
private bool ShouldShowVersionText(CommandSchema commandSchema, CommandInput commandInput) =>
|
||||||
|
commandSchema.IsVersionOptionAvailable && commandInput.IsVersionOptionSpecified;
|
||||||
|
|
||||||
|
private async ValueTask PromptDebuggerAsync()
|
||||||
|
{
|
||||||
|
using (_console.WithForegroundColor(ConsoleColor.Green))
|
||||||
{
|
{
|
||||||
Metadata = metadata;
|
var processId = ProcessEx.GetCurrentProcessId();
|
||||||
Configuration = configuration;
|
|
||||||
_console = console;
|
|
||||||
_typeActivator = typeActivator;
|
|
||||||
|
|
||||||
_commandBinder = new CommandBinder(typeActivator);
|
_console.Output.WriteLine(
|
||||||
|
$"Attach debugger to PID {processId} to continue."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsDebugModeEnabled(CommandInput commandInput) =>
|
// Try to also launch debugger ourselves (only works if VS is installed)
|
||||||
Configuration.IsDebugModeAllowed && commandInput.IsDebugDirectiveSpecified;
|
Debugger.Launch();
|
||||||
|
|
||||||
private bool IsPreviewModeEnabled(CommandInput commandInput) =>
|
while (!Debugger.IsAttached)
|
||||||
Configuration.IsPreviewModeAllowed && commandInput.IsPreviewDirectiveSpecified;
|
|
||||||
|
|
||||||
private bool ShouldShowHelpText(CommandSchema commandSchema, CommandInput commandInput) =>
|
|
||||||
commandSchema.IsHelpOptionAvailable && commandInput.IsHelpOptionSpecified ||
|
|
||||||
// Show help text also in case the fallback default command is
|
|
||||||
// executed without any arguments.
|
|
||||||
commandSchema == FallbackDefaultCommand.Schema &&
|
|
||||||
string.IsNullOrWhiteSpace(commandInput.CommandName) &&
|
|
||||||
!commandInput.Parameters.Any() &&
|
|
||||||
!commandInput.Options.Any();
|
|
||||||
|
|
||||||
private bool ShouldShowVersionText(CommandSchema commandSchema, CommandInput commandInput) =>
|
|
||||||
commandSchema.IsVersionOptionAvailable && commandInput.IsVersionOptionSpecified;
|
|
||||||
|
|
||||||
private async ValueTask PromptDebuggerAsync()
|
|
||||||
{
|
{
|
||||||
using (_console.WithForegroundColor(ConsoleColor.Green))
|
await Task.Delay(100);
|
||||||
{
|
}
|
||||||
var processId = ProcessEx.GetCurrentProcessId();
|
}
|
||||||
|
|
||||||
_console.Output.WriteLine(
|
private async ValueTask<int> RunAsync(ApplicationSchema applicationSchema, CommandInput commandInput)
|
||||||
$"Attach debugger to PID {processId} to continue."
|
{
|
||||||
);
|
// Handle debug directive
|
||||||
}
|
if (IsDebugModeEnabled(commandInput))
|
||||||
|
{
|
||||||
// Try to also launch debugger ourselves (only works if VS is installed)
|
await PromptDebuggerAsync();
|
||||||
Debugger.Launch();
|
|
||||||
|
|
||||||
while (!Debugger.IsAttached)
|
|
||||||
{
|
|
||||||
await Task.Delay(100);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask<int> RunAsync(ApplicationSchema applicationSchema, CommandInput commandInput)
|
// Handle preview directive
|
||||||
|
if (IsPreviewModeEnabled(commandInput))
|
||||||
{
|
{
|
||||||
// Handle debug directive
|
_console.Output.WriteCommandInput(commandInput);
|
||||||
if (IsDebugModeEnabled(commandInput))
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get the command schema that matches the input
|
||||||
|
var commandSchema =
|
||||||
|
applicationSchema.TryFindCommand(commandInput.CommandName) ??
|
||||||
|
applicationSchema.TryFindDefaultCommand() ??
|
||||||
|
FallbackDefaultCommand.Schema;
|
||||||
|
|
||||||
|
// Activate command instance
|
||||||
|
var commandInstance = commandSchema == FallbackDefaultCommand.Schema
|
||||||
|
? new FallbackDefaultCommand() // bypass activator
|
||||||
|
: (ICommand) _typeActivator.CreateInstance(commandSchema.Type);
|
||||||
|
|
||||||
|
// Assemble help context
|
||||||
|
var helpContext = new HelpContext(
|
||||||
|
Metadata,
|
||||||
|
applicationSchema,
|
||||||
|
commandSchema,
|
||||||
|
commandSchema.GetValues(commandInstance)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle help option
|
||||||
|
if (ShouldShowHelpText(commandSchema, commandInput))
|
||||||
|
{
|
||||||
|
_console.Output.WriteHelpText(helpContext);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle version option
|
||||||
|
if (ShouldShowVersionText(commandSchema, commandInput))
|
||||||
|
{
|
||||||
|
_console.Output.WriteLine(Metadata.Version);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starting from this point, we may produce exceptions that are meant for the
|
||||||
|
// end user of the application (i.e. invalid input, command exception, etc).
|
||||||
|
// Catch these exceptions here, print them to the console, and don't let them
|
||||||
|
// propagate further.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Bind and execute command
|
||||||
|
_commandBinder.Bind(commandInput, commandSchema, commandInstance);
|
||||||
|
await commandInstance.ExecuteAsync(_console);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (CliFxException ex)
|
||||||
|
{
|
||||||
|
_console.Error.WriteException(ex);
|
||||||
|
|
||||||
|
if (ex.ShowHelp)
|
||||||
{
|
{
|
||||||
await PromptDebuggerAsync();
|
_console.Output.WriteLine();
|
||||||
|
_console.Output.WriteHelpText(helpContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle preview directive
|
return ex.ExitCode;
|
||||||
if (IsPreviewModeEnabled(commandInput))
|
}
|
||||||
{
|
}
|
||||||
_console.Output.WriteCommandInput(commandInput);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to get the command schema that matches the input
|
/// <summary>
|
||||||
var commandSchema =
|
/// Runs the application with the specified command line arguments and environment variables.
|
||||||
applicationSchema.TryFindCommand(commandInput.CommandName) ??
|
/// Returns an exit code which indicates whether the application completed successfully.
|
||||||
applicationSchema.TryFindDefaultCommand() ??
|
/// </summary>
|
||||||
FallbackDefaultCommand.Schema;
|
/// <remarks>
|
||||||
|
/// When running WITHOUT debugger (i.e. in production), this method swallows all exceptions and
|
||||||
|
/// reports them to the console.
|
||||||
|
/// </remarks>
|
||||||
|
public async ValueTask<int> RunAsync(
|
||||||
|
IReadOnlyList<string> commandLineArguments,
|
||||||
|
IReadOnlyDictionary<string, string> environmentVariables)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Console colors may have already been overriden by the parent process,
|
||||||
|
// so we need to reset it to make sure that everything we write looks properly.
|
||||||
|
_console.ResetColor();
|
||||||
|
|
||||||
// Activate command instance
|
var applicationSchema = ApplicationSchema.Resolve(Configuration.CommandTypes);
|
||||||
var commandInstance = commandSchema == FallbackDefaultCommand.Schema
|
|
||||||
? new FallbackDefaultCommand() // bypass activator
|
|
||||||
: (ICommand) _typeActivator.CreateInstance(commandSchema.Type);
|
|
||||||
|
|
||||||
// Assemble help context
|
var commandInput = CommandInput.Parse(
|
||||||
var helpContext = new HelpContext(
|
commandLineArguments,
|
||||||
Metadata,
|
environmentVariables,
|
||||||
applicationSchema,
|
applicationSchema.GetCommandNames()
|
||||||
commandSchema,
|
|
||||||
commandSchema.GetValues(commandInstance)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle help option
|
return await RunAsync(applicationSchema, commandInput);
|
||||||
if (ShouldShowHelpText(commandSchema, commandInput))
|
|
||||||
{
|
|
||||||
_console.Output.WriteHelpText(helpContext);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle version option
|
|
||||||
if (ShouldShowVersionText(commandSchema, commandInput))
|
|
||||||
{
|
|
||||||
_console.Output.WriteLine(Metadata.Version);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starting from this point, we may produce exceptions that are meant for the
|
|
||||||
// end user of the application (i.e. invalid input, command exception, etc).
|
|
||||||
// Catch these exceptions here, print them to the console, and don't let them
|
|
||||||
// propagate further.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Bind and execute command
|
|
||||||
_commandBinder.Bind(commandInput, commandSchema, commandInstance);
|
|
||||||
await commandInstance.ExecuteAsync(_console);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
catch (CliFxException ex)
|
|
||||||
{
|
|
||||||
_console.Error.WriteException(ex);
|
|
||||||
|
|
||||||
if (ex.ShowHelp)
|
|
||||||
{
|
|
||||||
_console.Output.WriteLine();
|
|
||||||
_console.Output.WriteHelpText(helpContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ex.ExitCode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// To prevent the app from showing the annoying troubleshooting dialog on Windows,
|
||||||
/// <summary>
|
// we handle all exceptions ourselves and print them to the console.
|
||||||
/// Runs the application with the specified command line arguments and environment variables.
|
//
|
||||||
/// Returns an exit code which indicates whether the application completed successfully.
|
// We only want to do that if the app is running in production, which we infer
|
||||||
/// </summary>
|
// based on whether a debugger is attached to the process.
|
||||||
/// <remarks>
|
//
|
||||||
/// When running WITHOUT debugger (i.e. in production), this method swallows all exceptions and
|
// When not running in production, we want the IDE to show exceptions to the
|
||||||
/// reports them to the console.
|
// developer, so we don't swallow them in that case.
|
||||||
/// </remarks>
|
catch (Exception ex) when (!Debugger.IsAttached)
|
||||||
public async ValueTask<int> RunAsync(
|
|
||||||
IReadOnlyList<string> commandLineArguments,
|
|
||||||
IReadOnlyDictionary<string, string> environmentVariables)
|
|
||||||
{
|
{
|
||||||
try
|
_console.Error.WriteException(ex);
|
||||||
{
|
return 1;
|
||||||
// Console colors may have already been overriden by the parent process,
|
|
||||||
// so we need to reset it to make sure that everything we write looks properly.
|
|
||||||
_console.ResetColor();
|
|
||||||
|
|
||||||
var applicationSchema = ApplicationSchema.Resolve(Configuration.CommandTypes);
|
|
||||||
|
|
||||||
var commandInput = CommandInput.Parse(
|
|
||||||
commandLineArguments,
|
|
||||||
environmentVariables,
|
|
||||||
applicationSchema.GetCommandNames()
|
|
||||||
);
|
|
||||||
|
|
||||||
return await RunAsync(applicationSchema, commandInput);
|
|
||||||
}
|
|
||||||
// To prevent the app from showing the annoying troubleshooting dialog on Windows,
|
|
||||||
// we handle all exceptions ourselves and print them to the console.
|
|
||||||
//
|
|
||||||
// We only want to do that if the app is running in production, which we infer
|
|
||||||
// based on whether a debugger is attached to the process.
|
|
||||||
//
|
|
||||||
// When not running in production, we want the IDE to show exceptions to the
|
|
||||||
// developer, so we don't swallow them in that case.
|
|
||||||
catch (Exception ex) when (!Debugger.IsAttached)
|
|
||||||
{
|
|
||||||
_console.Error.WriteException(ex);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Runs the application with the specified command line arguments.
|
|
||||||
/// Environment variables are resolved automatically.
|
|
||||||
/// Returns an exit code which indicates whether the application completed successfully.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// When running WITHOUT debugger (i.e. in production), this method swallows all exceptions and
|
|
||||||
/// reports them to the console.
|
|
||||||
/// </remarks>
|
|
||||||
public async ValueTask<int> RunAsync(IReadOnlyList<string> commandLineArguments) => await RunAsync(
|
|
||||||
commandLineArguments,
|
|
||||||
// Use case-sensitive comparison because environment variables are
|
|
||||||
// case-sensitive on Linux and macOS (but not on Windows).
|
|
||||||
Environment
|
|
||||||
.GetEnvironmentVariables()
|
|
||||||
.ToDictionary<string, string>(StringComparer.Ordinal)
|
|
||||||
);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Runs the application.
|
|
||||||
/// Command line arguments and environment variables are resolved automatically.
|
|
||||||
/// Returns an exit code which indicates whether the application completed successfully.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// When running WITHOUT debugger (i.e. in production), this method swallows all exceptions and
|
|
||||||
/// reports them to the console.
|
|
||||||
/// </remarks>
|
|
||||||
public async ValueTask<int> RunAsync() => await RunAsync(
|
|
||||||
Environment.GetCommandLineArgs()
|
|
||||||
.Skip(1) // first element is the file path
|
|
||||||
.ToArray()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the application with the specified command line arguments.
|
||||||
|
/// Environment variables are resolved automatically.
|
||||||
|
/// Returns an exit code which indicates whether the application completed successfully.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// When running WITHOUT debugger (i.e. in production), this method swallows all exceptions and
|
||||||
|
/// reports them to the console.
|
||||||
|
/// </remarks>
|
||||||
|
public async ValueTask<int> RunAsync(IReadOnlyList<string> commandLineArguments) => await RunAsync(
|
||||||
|
commandLineArguments,
|
||||||
|
// Use case-sensitive comparison because environment variables are
|
||||||
|
// case-sensitive on Linux and macOS (but not on Windows).
|
||||||
|
Environment
|
||||||
|
.GetEnvironmentVariables()
|
||||||
|
.ToDictionary<string, string>(StringComparer.Ordinal)
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the application.
|
||||||
|
/// Command line arguments and environment variables are resolved automatically.
|
||||||
|
/// Returns an exit code which indicates whether the application completed successfully.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// When running WITHOUT debugger (i.e. in production), this method swallows all exceptions and
|
||||||
|
/// reports them to the console.
|
||||||
|
/// </remarks>
|
||||||
|
public async ValueTask<int> RunAsync() => await RunAsync(
|
||||||
|
Environment.GetCommandLineArgs()
|
||||||
|
.Skip(1) // first element is the file path
|
||||||
|
.ToArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user