59 Commits
2.0.3 ... 2.2.1

Author SHA1 Message Date
Oleksii Holub
aa3094ee54 Update version 2022-01-16 19:29:50 +02:00
Tyrrrz
712580e3d7 Update my name to match correct spelling 2022-01-15 03:24:06 +02:00
AliReZa Sabouri
c08102f85f Show default values for optional parameters (#122) 2022-01-11 05:22:13 -08:00
Tyrrrz
5e684c8b36 Update version 2022-01-11 00:40:30 +02:00
Tyrrrz
300ae70564 Update NuGet packages 2022-01-11 00:39:19 +02:00
Tyrrrz
76f0c77f1e Update readme 2022-01-11 00:32:56 +02:00
Tyrrrz
0f7cea4ed1 Add some more analyzer tests 2022-01-10 23:56:54 +02:00
Tyrrrz
32ee0b2bd6 Add test for optional parameters 2022-01-10 23:48:38 +02:00
Tyrrrz
4ff1e1d3e1 Cleanup 2022-01-10 23:41:28 +02:00
AliReZa Sabouri
8e96d2701d Add support for optional parameters (#119) 2022-01-10 13:11:04 -08:00
Tyrrrz
8e307df231 More cleanup 2022-01-10 16:55:43 +02:00
Tyrrrz
ff38f4916a Cleanup 2022-01-10 16:45:41 +02:00
AliReZa Sabouri
7cbbb220b4 Fix tests for default interface members (#121) 2022-01-09 20:29:57 -08:00
AliReZa Sabouri
ae2d4299f0 Add multiple inheritance support through interfaces (#120) 2022-01-09 08:11:42 -08:00
Tyrrrz
21bc69d116 Make projects not packable by default 2022-01-04 22:48:33 +02:00
Tyrrrz
05a70175cc Update version 2022-01-04 22:35:59 +02:00
Tyrrrz
33ec2eb3a0 Cleanup 2022-01-04 22:31:50 +02:00
David Fallah
f6ef6cd4c0 Fix ordering of parameters within command help usage (#118) 2022-01-04 12:12:17 -08:00
Tyrrrz
a9ef693dc1 Share more stuff 2021-12-11 00:11:20 +02:00
Tyrrrz
98bbd666dc Update badges 2021-12-10 23:22:22 +02:00
Tyrrrz
4e7ed830f8 Move to shared workflows 2021-12-10 23:21:38 +02:00
Tyrrrz
ef87ff76fc Use top-level statements in demo 2021-12-08 23:52:33 +02:00
Tyrrrz
2feeb21270 C#10ify 2021-12-08 23:43:35 +02:00
Tyrrrz
9990387cfa Update readme 2021-12-05 22:20:00 +02:00
Tyrrrz
bc1bdca7c6 Update nuget packages 2021-12-05 22:05:21 +02:00
Tyrrrz
2a992d37df Update readme 2021-12-05 21:59:53 +02:00
Tyrrrz
15c87aecbb Update CI to .NET 6 2021-11-08 23:34:14 +02:00
Alexey Golub
10a46451ac Update Readme.md 2021-09-30 15:21:11 -07:00
Alexey Golub
e4c6a4174b Update readme 2021-09-04 04:20:38 -07:00
Alexey Golub
4c65f7bbee Update readme 2021-08-30 18:34:26 -07:00
Alexey Golub
5f21de0df5 Refactor webhook in CD 2021-08-28 10:30:58 -07:00
Alexey Golub
9b01b67d98 Update CD 2021-07-28 15:13:09 -07:00
Tyrrrz
4508f5e211 Add Discord 2021-07-26 19:30:54 +03:00
Alex Rosenfeld
f0cbc46df4 Add ReadKey to IConsole (#111)
Co-authored-by: Alexey Golub <tyrrrrrr@gmail.com>
2021-07-23 11:46:00 -07:00
Alex Rosenfeld
6c96e9e173 Add a clear console function (#110) 2021-07-19 04:33:07 -07:00
Tyrrrz
51cca36d2a Update version 2021-07-17 21:37:32 +03:00
Tyrrrz
84672c92f6 Unwrap TargetInvocationException to provide more user-friendly errors when binding fails 2021-07-17 21:32:15 +03:00
Tyrrrz
b1d01898b6 Add test for preamble omission 2021-07-10 19:43:21 +03:00
Tyrrrz
441a47a1a8 Update version 2021-07-09 22:23:46 +03:00
Tyrrrz
8abd7219a1 Better shimming in NoPreambleEncoding 2021-07-09 22:00:31 +03:00
Tyrrrz
df73a0bfe8 Update GitHib issue forms 2021-06-24 21:40:08 +03:00
Tyrrrz
55d12dc721 Add readme to package 2021-06-17 20:36:35 +03:00
Alexey Golub
a6ee44c1bb Fix typo in readme 2021-06-13 06:19:26 -07:00
Tyrrrz
76816e22f1 Use Basic.Reference.Assemblies to simplify reference resolving for dynamic assemblies in tests
Note: bumped `Microsoft.CodeAnalysis.CSharp` in test projects, but didn't touch the one in CliFx.Analyzers as it may have unintended side-effects.
2021-05-10 21:10:42 +03:00
Tyrrrz
daf25e59d6 Fix deprecation warning 2021-05-10 21:06:36 +03:00
Tyrrrz
f2b4e53615 Update version 2021-04-24 20:59:10 +03:00
Tyrrrz
2d519ab190 Remove the usage of ConsoleColor.DarkGray because it looks bad in some terminals
Fixes #104
2021-04-24 20:48:06 +03:00
Tyrrrz
2d479c9cb6 Refactor 2021-04-24 20:43:35 +03:00
Tyrrrz
2bb7e13e51 Use issue forms 2021-04-22 22:11:39 +03:00
Tyrrrz
6e1dfdcdd4 Update readme 2021-04-22 21:08:16 +03:00
Tyrrrz
5ba647e5c1 Update readme 2021-04-22 21:05:35 +03:00
Tyrrrz
853492695f Update readme 2021-04-22 21:04:36 +03:00
Robert Dailey
d5d72c7c50 Show choices for nullable enums in enumerable (#105) 2021-04-22 15:28:33 +03:00
Tyrrrz
d676b5832e Fix discrepancies in unicode handling between ConsoleWriter and Console.Write(...) 2021-04-21 03:16:18 +03:00
Tyrrrz
28097afc1e Update NuGet packages 2021-04-18 19:38:35 +03:00
Tyrrrz
fda96586f3 Update NuGet.config 2021-04-17 21:23:19 +03:00
Tyrrrz
fc5af8dbbc Don't write default value in help text for types that don't override ToString() 2021-04-16 23:28:39 +03:00
Tyrrrz
4835e64388 Remove GHA workarounds 2021-04-13 22:29:15 +03:00
Tyrrrz
0999c33f93 Add NuGet.config 2021-04-13 22:20:07 +03:00
156 changed files with 8979 additions and 8230 deletions

3
.github/FUNDING.yml vendored
View File

@@ -1,3 +0,0 @@
github: Tyrrrz
patreon: Tyrrrz
custom: ['buymeacoffee.com/Tyrrrz', 'tyrrrz.me/donate']

42
.github/ISSUE_TEMPLATE/bug-report.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: 🐞 Bug report
description: Report broken functionality.
labels: [bug]
body:
- type: markdown
attributes:
value: |
🧐 **Guidelines:**
- Search through [existing issues](https://github.com/Tyrrrz/CliFx/issues?q=is%3Aissue) first to ensure that this bug has not been reported before.
- Write a descriptive title for your issue. Avoid generic or vague titles such as "Something's not working" or "A couple of problems".
- Keep your issue focused on one single problem. If you have multiple bug reports, please create separate issues for each of them.
- Provide as much context as possible in the details section. Include screenshots, screen recordings, links, references, or anything else you may consider relevant.
- If you want to ask a question instead of reporting a bug, please use [discussions](https://github.com/Tyrrrz/CliFx/discussions/new) instead.
- type: input
attributes:
label: Version
description: Which version of CliFx does this bug affect?
placeholder: ver X.Y.Z
validations:
required: true
- type: textarea
attributes:
label: Details
description: Clear and thorough explanation of the bug.
placeholder: I was doing X expecting Y to happen, but Z happened instead.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: Minimum steps required to reproduce the bug.
placeholder: |
- Step 1
- Step 2
- Step 3
validations:
required: true

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 💬 Discord server
url: https://discord.gg/2SUWKFnHSm
about: Chat with the project community.
- name: 🗨 Discussions
url: https://github.com/Tyrrrz/CliFx/discussions/new
about: Ask and answer questions.

View File

@@ -0,0 +1,22 @@
name: ✨ Feature request
description: Request a new feature.
labels: [enhancement]
body:
- type: markdown
attributes:
value: |
🧐 **Guidelines:**
- Search through [existing issues](https://github.com/Tyrrrz/CliFx/issues?q=is%3Aissue) first to ensure that this feature has not been requested before.
- Write a descriptive title for your issue. Avoid generic or vague titles such as "Some suggestions" or "Ideas for improvement".
- Keep your issue focused on one single problem. If you have multiple feature requests, please create separate issues for each of them.
- Provide as much context as possible in the details section. Include screenshots, screen recordings, links, references, or anything else you may consider relevant.
- If you want to ask a question instead of requesting a feature, please use [discussions](https://github.com/Tyrrrz/CliFx/discussions/new) instead.
- type: textarea
attributes:
label: Details
description: Clear and thorough explanation of the feature you have in mind.
validations:
required: true

View File

@@ -1,27 +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 nuget locals all --clear
dotnet pack CliFx --configuration Release
- name: Deploy
run: dotnet nuget push CliFx/bin/Release/*.nupkg -s nuget.org -k ${{ secrets.NUGET_TOKEN }}

View File

@@ -1,30 +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 nuget locals all --clear
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
View 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 }}

View File

@@ -1,3 +1,34 @@
### v2.2.1 (16-Jan-2022)
- Fixed an issue which caused help text to not show default values for optional parameters. (Thanks [@AliReZa Sabouri](https://github.com/alirezanet))
### v2.2 (11-Jan-2022)
- Added support for optional parameters. A parameter can be marked as optional by setting `IsRequired = false` on the attribute. Only one parameter is allowed to be optional and such parameter must be the last in order. (Thanks [@AliReZa Sabouri](https://github.com/alirezanet))
- Fixed an issue where parameters and options bound to properties implemented as default interface members were not working correctly. (Thanks [@AliReZa Sabouri](https://github.com/alirezanet))
### 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)
- 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.
### v2.0.5 (09-Jul-2021)
- Fixed an issue where calling `IConsole.Output.Encoding.EncodingName` and some other members threw an exception.
- Added readme file to the package.
### v2.0.4 (24-Apr-2021)
- Fixed an issue where output and error streams in `SystemConsole` defaulted to UTF8 encoding with BOM when the application was running with UTF8 codepage. `ConsoleWriter` will now discard preamble from the specified encoding. This fix brings the behavior of `SystemConsole` in line with .NET's own `System.Console` which also discards preamble for output and error streams.
- Fixed an issue where help text tried to show default values for parameters and options whose type does not override `ToString()` method.
- Fixed an issue where help text didn't show default values for parameters and options whose type is an enumerable of nullable enums. (Thanks [@Robert Dailey](https://github.com/rcdailey))
- Fixed an issue where specific parts of the help text weren't legible in some terminals due to low color resolution. Removed the usage of `ConsoleColor.DarkGray` in help text.
### v2.0.3 (09-Apr-2021)
- Improved help text by showing valid values for non-scalar enum parameters and options. (Thanks [@Robert Dailey](https://github.com/rcdailey))

View File

@@ -1,9 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<TargetFramework>net6.0</TargetFramework>
<CollectCoverage>true</CollectCoverage>
<CoverletOutputFormat>opencover</CoverletOutputFormat>
</PropertyGroup>
@@ -13,13 +11,14 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Basic.Reference.Assemblies" Version="1.2.4" />
<PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" />
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.4.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="FluentAssertions" Version="6.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<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>

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class CommandMustBeAnnotatedAnalyzerSpecs
{
public class CommandMustBeAnnotatedAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new CommandMustBeAnnotatedAnalyzer();
[Fact]
@@ -68,5 +68,4 @@ public class Foo
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class CommandMustImplementInterfaceAnalyzerSpecs
{
public class CommandMustImplementInterfaceAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new CommandMustImplementInterfaceAnalyzer();
[Fact]
@@ -54,5 +54,4 @@ public class Foo
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -4,10 +4,10 @@ using FluentAssertions;
using Microsoft.CodeAnalysis.Diagnostics;
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()
{
@@ -27,5 +27,4 @@ namespace CliFx.Analyzers.Tests
// Assert
diagnosticIds.Should().OnlyHaveUniqueItems();
}
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class OptionMustBeInsideCommandAnalyzerSpecs
{
public class OptionMustBeInsideCommandAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustBeInsideCommandAnalyzer();
[Fact]
@@ -76,5 +76,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class OptionMustHaveNameOrShortNameAnalyzerSpecs
{
public class OptionMustHaveNameOrShortNameAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveNameOrShortNameAnalyzer();
[Fact]
@@ -82,5 +82,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class OptionMustHaveUniqueNameAnalyzerSpecs
{
public class OptionMustHaveUniqueNameAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveUniqueNameAnalyzer();
[Fact]
@@ -88,5 +88,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class OptionMustHaveUniqueShortNameAnalyzerSpecs
{
public class OptionMustHaveUniqueShortNameAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveUniqueShortNameAnalyzer();
[Fact]
@@ -110,5 +110,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class OptionMustHaveValidConverterAnalyzerSpecs
{
public class OptionMustHaveValidConverterAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidConverterAnalyzer();
[Fact]
@@ -92,5 +92,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class OptionMustHaveValidNameAnalyzerSpecs
{
public class OptionMustHaveValidNameAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidNameAnalyzer();
[Fact]
@@ -101,5 +101,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class OptionMustHaveValidShortNameAnalyzerSpecs
{
public class OptionMustHaveValidShortNameAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidShortNameAnalyzer();
[Fact]
@@ -82,5 +82,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class OptionMustHaveValidValidatorsAnalyzerSpecs
{
public class OptionMustHaveValidValidatorsAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidValidatorsAnalyzer();
[Fact]
@@ -92,5 +92,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class ParameterMustBeInsideCommandAnalyzerSpecs
{
public class ParameterMustBeInsideCommandAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeInsideCommandAnalyzer();
[Fact]
@@ -76,5 +76,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -0,0 +1,94 @@
using CliFx.Analyzers.Tests.Utils;
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests;
public class ParameterMustBeLastIfNonRequiredAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeLastIfNonRequiredAnalyzer();
[Fact]
public void Analyzer_reports_an_error_if_a_non_required_parameter_is_not_the_last_in_order()
{
// Arrange
// language=cs
const string code = @"
[Command]
public class MyCommand : ICommand
{
[CommandParameter(0, Name = ""foo"", IsRequired = false)]
public string Foo { get; set; }
[CommandParameter(1, Name = ""bar"")]
public string Bar { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}";
// Act & assert
Analyzer.Should().ProduceDiagnostics(code);
}
[Fact]
public void Analyzer_does_not_report_an_error_if_a_non_required_parameter_is_the_last_in_order()
{
// Arrange
// language=cs
const string code = @"
[Command]
public class MyCommand : ICommand
{
[CommandParameter(0, Name = ""foo"")]
public string Foo { get; set; }
[CommandParameter(1, Name = ""bar"", IsRequired = false)]
public string Bar { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}";
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
[Fact]
public void Analyzer_does_not_report_an_error_if_no_non_required_parameters_are_defined()
{
// Arrange
// language=cs
const string code = @"
[Command]
public class MyCommand : ICommand
{
[CommandParameter(0, Name = ""foo"")]
public string Foo { get; set; }
[CommandParameter(1, Name = ""bar"", IsRequired = true)]
public string Bar { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}";
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
[Fact]
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
{
// Arrange
// language=cs
const string code = @"
[Command]
public class MyCommand : ICommand
{
public string Foo { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}";
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}

View File

@@ -2,14 +2,14 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class ParameterMustBeLastIfNonScalarAnalyzerSpecs
{
public class ParameterMustBeLastIfNonScalarAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeLastIfNonScalarAnalyzer();
[Fact]
public void Analyzer_reports_an_error_if_a_non_scalar_parameter_is_not_last_in_order()
public void Analyzer_reports_an_error_if_a_non_scalar_parameter_is_not_the_last_in_order()
{
// Arrange
// language=cs
@@ -31,7 +31,7 @@ public class MyCommand : ICommand
}
[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_the_last_in_order()
{
// Arrange
// language=cs
@@ -91,5 +91,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -0,0 +1,94 @@
using CliFx.Analyzers.Tests.Utils;
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests;
public class ParameterMustBeSingleIfNonRequiredAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeSingleIfNonRequiredAnalyzer();
[Fact]
public void Analyzer_reports_an_error_if_more_than_one_non_required_parameters_are_defined()
{
// Arrange
// language=cs
const string code = @"
[Command]
public class MyCommand : ICommand
{
[CommandParameter(0, IsRequired = false)]
public string Foo { get; set; }
[CommandParameter(1, IsRequired = false)]
public string Bar { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}";
// Act & assert
Analyzer.Should().ProduceDiagnostics(code);
}
[Fact]
public void Analyzer_does_not_report_an_error_if_only_one_non_required_parameter_is_defined()
{
// Arrange
// language=cs
const string code = @"
[Command]
public class MyCommand : ICommand
{
[CommandParameter(0)]
public string Foo { get; set; }
[CommandParameter(1, IsRequired = false)]
public string Bar { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}";
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
[Fact]
public void Analyzer_does_not_report_an_error_if_no_non_required_parameters_are_defined()
{
// Arrange
// language=cs
const string code = @"
[Command]
public class MyCommand : ICommand
{
[CommandParameter(0)]
public string Foo { get; set; }
[CommandParameter(1, IsRequired = true)]
public string Bar { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}";
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
[Fact]
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
{
// Arrange
// language=cs
const string code = @"
[Command]
public class MyCommand : ICommand
{
public string Foo { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}";
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class ParameterMustBeSingleIfNonScalarAnalyzerSpecs
{
public class ParameterMustBeSingleIfNonScalarAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeSingleIfNonScalarAnalyzer();
[Fact]
@@ -91,5 +91,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class ParameterMustHaveUniqueNameAnalyzerSpecs
{
public class ParameterMustHaveUniqueNameAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveUniqueNameAnalyzer();
[Fact]
@@ -69,5 +69,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class ParameterMustHaveUniqueOrderAnalyzerSpecs
{
public class ParameterMustHaveUniqueOrderAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveUniqueOrderAnalyzer();
[Fact]
@@ -69,5 +69,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class ParameterMustHaveValidConverterAnalyzerSpecs
{
public class ParameterMustHaveValidConverterAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveValidConverterAnalyzer();
[Fact]
@@ -92,5 +92,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class ParameterMustHaveValidValidatorsAnalyzerSpecs
{
public class ParameterMustHaveValidValidatorsAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveValidValidatorsAnalyzer();
[Fact]
@@ -92,5 +92,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Xunit;
namespace CliFx.Analyzers.Tests
namespace CliFx.Analyzers.Tests;
public class SystemConsoleShouldBeAvoidedAnalyzerSpecs
{
public class SystemConsoleShouldBeAvoidedAnalyzerSpecs
{
private static DiagnosticAnalyzer Analyzer { get; } = new SystemConsoleShouldBeAvoidedAnalyzer();
[Fact]
@@ -123,5 +123,4 @@ public class MyCommand : ICommand
// Act & assert
Analyzer.Should().NotProduceDiagnostics(code);
}
}
}

View File

@@ -2,8 +2,8 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Text;
using Basic.Reference.Assemblies;
using FluentAssertions.Execution;
using FluentAssertions.Primitives;
using Microsoft.CodeAnalysis;
@@ -11,10 +11,10 @@ using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
namespace CliFx.Analyzers.Tests.Utils
namespace CliFx.Analyzers.Tests.Utils;
internal class AnalyzerAssertions : ReferenceTypeAssertions<DiagnosticAnalyzer, AnalyzerAssertions>
{
internal class AnalyzerAssertions : ReferenceTypeAssertions<DiagnosticAnalyzer, AnalyzerAssertions>
{
protected override string Identifier { get; } = "analyzer";
public AnalyzerAssertions(DiagnosticAnalyzer analyzer)
@@ -58,14 +58,8 @@ namespace CliFx.Analyzers.Tests.Utils
var compilation = CSharpCompilation.Create(
"CliFxTests_DynamicAssembly_" + Guid.NewGuid(),
new[] {ast},
new[]
{
MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location),
MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location),
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location)
},
ReferenceAssemblies.Net50
.Append(MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location)),
// DLL to avoid having to define the Main() method
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
);
@@ -165,10 +159,9 @@ namespace CliFx.Analyzers.Tests.Utils
return new FailReason(buffer.ToString());
});
}
}
internal static class AnalyzerAssertionsExtensions
{
public static AnalyzerAssertions Should(this DiagnosticAnalyzer analyzer) => new(analyzer);
}
}
internal static class AnalyzerAssertionsExtensions
{
public static AnalyzerAssertions Should(this DiagnosticAnalyzer analyzer) => new(analyzer);
}

View File

@@ -3,10 +3,10 @@ using CliFx.Analyzers.Utils.Extensions;
using Microsoft.CodeAnalysis;
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; }
@@ -36,5 +36,4 @@ namespace CliFx.Analyzers
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
}
}
}

View File

@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.4.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
</ItemGroup>
</Project>

View File

@@ -5,11 +5,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CommandMustBeAnnotatedAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CommandMustBeAnnotatedAnalyzer : AnalyzerBase
{
public CommandMustBeAnnotatedAnalyzer()
: base(
$"Commands must be annotated with `{SymbolNames.CliFxCommandAttribute}`",
@@ -50,5 +50,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandleClassDeclaration(Analyze);
}
}
}

View File

@@ -5,11 +5,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CommandMustImplementInterfaceAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CommandMustImplementInterfaceAnalyzer : AnalyzerBase
{
public CommandMustImplementInterfaceAnalyzer()
: base(
$"Commands must implement `{SymbolNames.CliFxCommandInterface}` interface",
@@ -44,5 +44,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandleClassDeclaration(Analyze);
}
}
}

View File

@@ -3,10 +3,10 @@ using Microsoft.CodeAnalysis;
using System.Linq;
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; }
@@ -26,10 +26,10 @@ namespace CliFx.Analyzers.ObjectModel
ConverterType = converterType;
ValidatorTypes = validatorTypes;
}
}
}
internal partial class CommandOptionSymbol
{
internal partial class CommandOptionSymbol
{
private static AttributeData? TryGetOptionAttribute(IPropertySymbol property) =>
property
.GetAttributes()
@@ -71,13 +71,11 @@ namespace CliFx.Analyzers.ObjectModel
{
var attribute = TryGetOptionAttribute(property);
if (attribute is null)
return null;
return FromAttribute(attribute);
return attribute is not null
? FromAttribute(attribute)
: null;
}
public static bool IsOptionProperty(IPropertySymbol property) =>
TryGetOptionAttribute(property) is not null;
}
}

View File

@@ -3,14 +3,16 @@ using System.Linq;
using CliFx.Analyzers.Utils.Extensions;
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 bool? IsRequired { get; }
public ITypeSymbol? ConverterType { get; }
public IReadOnlyList<ITypeSymbol> ValidatorTypes { get; }
@@ -18,18 +20,20 @@ namespace CliFx.Analyzers.ObjectModel
public CommandParameterSymbol(
int order,
string? name,
bool? isRequired,
ITypeSymbol? converterType,
IReadOnlyList<ITypeSymbol> validatorTypes)
{
Order = order;
Name = name;
IsRequired = isRequired;
ConverterType = converterType;
ValidatorTypes = validatorTypes;
}
}
}
internal partial class CommandParameterSymbol
{
internal partial class CommandParameterSymbol
{
private static AttributeData? TryGetParameterAttribute(IPropertySymbol property) =>
property
.GetAttributes()
@@ -37,7 +41,7 @@ namespace CliFx.Analyzers.ObjectModel
private static CommandParameterSymbol FromAttribute(AttributeData attribute)
{
var order = (int) attribute
var order = (int)attribute
.ConstructorArguments
.Select(a => a.Value)
.First()!;
@@ -48,6 +52,12 @@ namespace CliFx.Analyzers.ObjectModel
.Select(a => a.Value.Value)
.FirstOrDefault() as string;
var isRequired = attribute
.NamedArguments
.Where(a => a.Key == "IsRequired")
.Select(a => a.Value.Value)
.FirstOrDefault() as bool?;
var converter = attribute
.NamedArguments
.Where(a => a.Key == "Converter")
@@ -63,20 +73,18 @@ namespace CliFx.Analyzers.ObjectModel
.Cast<ITypeSymbol>()
.ToArray();
return new CommandParameterSymbol(order, name, converter, validators);
return new CommandParameterSymbol(order, name, isRequired, converter, validators);
}
public static CommandParameterSymbol? TryResolve(IPropertySymbol property)
{
var attribute = TryGetParameterAttribute(property);
if (attribute is null)
return null;
return FromAttribute(attribute);
return attribute is not null
? FromAttribute(attribute)
: null;
}
public static bool IsParameterProperty(IPropertySymbol property) =>
TryGetParameterAttribute(property) is not null;
}
}

View File

@@ -1,7 +1,7 @@
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 CliFxCommandParameterAttribute = "CliFx.Attributes.CommandParameterAttribute";
@@ -11,5 +11,4 @@
public const string CliFxBindingConverterClass = "CliFx.Extensibility.BindingConverter<T>";
public const string CliFxBindingValidatorInterface = "CliFx.Extensibility.IBindingValidator";
public const string CliFxBindingValidatorClass = "CliFx.Extensibility.BindingValidator<T>";
}
}

View File

@@ -5,11 +5,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustBeInsideCommandAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustBeInsideCommandAnalyzer : AnalyzerBase
{
public OptionMustBeInsideCommandAnalyzer()
: base(
"Options must be defined inside commands",
@@ -47,5 +47,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}
}

View File

@@ -4,11 +4,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveNameOrShortNameAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveNameOrShortNameAnalyzer : AnalyzerBase
{
public OptionMustHaveNameOrShortNameAnalyzer()
: base(
"Options must have either a name or short name specified",
@@ -36,5 +36,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}
}

View File

@@ -6,11 +6,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveUniqueNameAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveUniqueNameAnalyzer : AnalyzerBase
{
public OptionMustHaveUniqueNameAnalyzer()
: base(
"Options must have unique names",
@@ -61,5 +61,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}
}

View File

@@ -5,11 +5,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveUniqueShortNameAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveUniqueShortNameAnalyzer : AnalyzerBase
{
public OptionMustHaveUniqueShortNameAnalyzer()
: base(
"Options must have unique short names",
@@ -60,5 +60,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}
}

View File

@@ -5,11 +5,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveValidConverterAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveValidConverterAnalyzer : AnalyzerBase
{
public OptionMustHaveValidConverterAnalyzer()
: base(
$"Option converters must derive from `{SymbolNames.CliFxBindingConverterClass}`",
@@ -46,5 +46,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}
}

View File

@@ -4,11 +4,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveValidNameAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveValidNameAnalyzer : AnalyzerBase
{
public OptionMustHaveValidNameAnalyzer()
: base(
"Options must have valid names",
@@ -39,5 +39,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}
}

View File

@@ -4,11 +4,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveValidShortNameAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveValidShortNameAnalyzer : AnalyzerBase
{
public OptionMustHaveValidShortNameAnalyzer()
: base(
"Option short names must be letter characters",
@@ -39,5 +39,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}
}

View File

@@ -5,11 +5,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveValidValidatorsAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveValidValidatorsAnalyzer : AnalyzerBase
{
public OptionMustHaveValidValidatorsAnalyzer()
: base(
$"Option validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`",
@@ -48,5 +48,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}
}

View File

@@ -5,11 +5,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustBeInsideCommandAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustBeInsideCommandAnalyzer : AnalyzerBase
{
public ParameterMustBeInsideCommandAnalyzer()
: base(
"Parameters must be defined inside commands",
@@ -47,5 +47,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}
}

View File

@@ -0,0 +1,60 @@
using System.Linq;
using CliFx.Analyzers.ObjectModel;
using CliFx.Analyzers.Utils.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustBeLastIfNonRequiredAnalyzer : AnalyzerBase
{
public ParameterMustBeLastIfNonRequiredAnalyzer()
: base(
"Parameters marked as non-required must be the last in order",
"This parameter is non-required so it must be the last in order (its order must be highest within the command).")
{
}
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property)
{
if (property.ContainingType is null)
return;
var parameter = CommandParameterSymbol.TryResolve(property);
if (parameter is null)
return;
if (parameter.IsRequired != false)
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;
if (otherParameter.Order > parameter.Order)
{
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
}
}
}
public override void Initialize(AnalysisContext context)
{
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}

View File

@@ -5,15 +5,15 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustBeLastIfNonScalarAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustBeLastIfNonScalarAnalyzer : AnalyzerBase
{
public ParameterMustBeLastIfNonScalarAnalyzer()
: 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).")
"Parameters of non-scalar types must be the last in order",
"This parameter has a non-scalar type so it must be the last in order (its order must be highest within the command).")
{
}
@@ -64,5 +64,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}
}

View File

@@ -0,0 +1,60 @@
using System.Linq;
using CliFx.Analyzers.ObjectModel;
using CliFx.Analyzers.Utils.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustBeSingleIfNonRequiredAnalyzer : AnalyzerBase
{
public ParameterMustBeSingleIfNonRequiredAnalyzer()
: base(
"Parameters marked as non-required are limited to one per command",
"This parameter is non-required so it must be the only such parameter in the command.")
{
}
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property)
{
if (property.ContainingType is null)
return;
var parameter = CommandParameterSymbol.TryResolve(property);
if (parameter is null)
return;
if (parameter.IsRequired != false)
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;
if (otherParameter.IsRequired == false)
{
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
}
}
}
public override void Initialize(AnalysisContext context)
{
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}

View File

@@ -5,11 +5,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustBeSingleIfNonScalarAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustBeSingleIfNonScalarAnalyzer : AnalyzerBase
{
public ParameterMustBeSingleIfNonScalarAnalyzer()
: base(
"Parameters of non-scalar types are limited to one per command",
@@ -62,5 +62,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}
}

View File

@@ -6,11 +6,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustHaveUniqueNameAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustHaveUniqueNameAnalyzer : AnalyzerBase
{
public ParameterMustHaveUniqueNameAnalyzer()
: base(
"Parameters must have unique names",
@@ -61,5 +61,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}
}

View File

@@ -5,11 +5,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustHaveUniqueOrderAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustHaveUniqueOrderAnalyzer : AnalyzerBase
{
public ParameterMustHaveUniqueOrderAnalyzer()
: base(
"Parameters must have unique order",
@@ -54,5 +54,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}
}

View File

@@ -5,11 +5,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustHaveValidConverterAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustHaveValidConverterAnalyzer : AnalyzerBase
{
public ParameterMustHaveValidConverterAnalyzer()
: base(
$"Parameter converters must derive from `{SymbolNames.CliFxBindingConverterClass}`",
@@ -46,5 +46,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}
}

View File

@@ -5,11 +5,11 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustHaveValidValidatorsAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustHaveValidValidatorsAnalyzer : AnalyzerBase
{
public ParameterMustHaveValidValidatorsAnalyzer()
: base(
$"Parameter validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`",
@@ -48,5 +48,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.HandlePropertyDeclaration(Analyze);
}
}
}

View File

@@ -6,11 +6,11 @@ using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class SystemConsoleShouldBeAvoidedAnalyzer : AnalyzerBase
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class SystemConsoleShouldBeAvoidedAnalyzer : AnalyzerBase
{
public SystemConsoleShouldBeAvoidedAnalyzer()
: base(
$"Avoid calling `System.Console` where `{SymbolNames.CliFxConsoleInterface}` is available",
@@ -74,5 +74,4 @@ namespace CliFx.Analyzers
base.Initialize(context);
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.SimpleMemberAccessExpression);
}
}
}

View File

@@ -4,10 +4,10 @@ using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
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::`
@@ -49,5 +49,4 @@ namespace CliFx.Analyzers.Utils.Extensions
analyze(ctx, propertyDeclaration, property);
}, SyntaxKind.PropertyDeclaration);
}
}
}

View File

@@ -1,9 +1,9 @@
using System;
namespace CliFx.Analyzers.Utils.Extensions
namespace CliFx.Analyzers.Utils.Extensions;
internal static class StringExtensions
{
internal static class StringExtensions
{
public static string TrimEnd(
this string str,
string sub,
@@ -14,5 +14,4 @@ namespace CliFx.Analyzers.Utils.Extensions
return str;
}
}
}

View File

@@ -4,10 +4,10 @@ using BenchmarkDotNet.Attributes;
using CliFx.Attributes;
using CliFx.Infrastructure;
namespace CliFx.Benchmarks
namespace CliFx.Benchmarks;
public partial class Benchmarks
{
public partial class Benchmarks
{
[Command]
public class CliFxCommand : ICommand
{
@@ -29,5 +29,4 @@ namespace CliFx.Benchmarks
.AddCommand<CliFxCommand>()
.Build()
.RunAsync(Arguments, new Dictionary<string, string>());
}
}

View File

@@ -1,10 +1,10 @@
using BenchmarkDotNet.Attributes;
using clipr;
namespace CliFx.Benchmarks
namespace CliFx.Benchmarks;
public partial class Benchmarks
{
public partial class Benchmarks
{
public class CliprCommand
{
[NamedArgument('s', "str")]
@@ -23,5 +23,4 @@ namespace CliFx.Benchmarks
[Benchmark(Description = "Clipr")]
public void ExecuteWithClipr() => CliParser.Parse<CliprCommand>(Arguments).Execute();
}
}

View File

@@ -1,10 +1,10 @@
using BenchmarkDotNet.Attributes;
using Cocona;
namespace CliFx.Benchmarks
namespace CliFx.Benchmarks;
public partial class Benchmarks
{
public partial class Benchmarks
{
public class CoconaCommand
{
public void Execute(
@@ -20,5 +20,4 @@ namespace CliFx.Benchmarks
[Benchmark(Description = "Cocona")]
public void ExecuteWithCocona() => CoconaApp.Run<CoconaCommand>(Arguments);
}
}

View File

@@ -1,10 +1,10 @@
using BenchmarkDotNet.Attributes;
using CommandLine;
namespace CliFx.Benchmarks
namespace CliFx.Benchmarks;
public partial class Benchmarks
{
public partial class Benchmarks
{
public class CommandLineParserCommand
{
[Option('s', "str")]
@@ -26,5 +26,4 @@ namespace CliFx.Benchmarks
new Parser()
.ParseArguments(Arguments, typeof(CommandLineParserCommand))
.WithParsed<CommandLineParserCommand>(c => c.Execute());
}
}

View File

@@ -1,10 +1,10 @@
using BenchmarkDotNet.Attributes;
using McMaster.Extensions.CommandLineUtils;
namespace CliFx.Benchmarks
namespace CliFx.Benchmarks;
public partial class Benchmarks
{
public partial class Benchmarks
{
public class McMasterCommand
{
[Option("--str|-s")]
@@ -21,5 +21,4 @@ namespace CliFx.Benchmarks
[Benchmark(Description = "McMaster.Extensions.CommandLineUtils")]
public int ExecuteWithMcMaster() => CommandLineApplication.Execute<McMasterCommand>(Arguments);
}
}

View File

@@ -1,10 +1,10 @@
using BenchmarkDotNet.Attributes;
using PowerArgs;
namespace CliFx.Benchmarks
namespace CliFx.Benchmarks;
public partial class Benchmarks
{
public partial class Benchmarks
{
public class PowerArgsCommand
{
[ArgShortcut("--str"), ArgShortcut("-s")]
@@ -23,5 +23,4 @@ namespace CliFx.Benchmarks
[Benchmark(Description = "PowerArgs")]
public void ExecuteWithPowerArgs() => Args.InvokeMain<PowerArgsCommand>(Arguments);
}
}

View File

@@ -3,10 +3,10 @@ using System.CommandLine.Invocation;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
namespace CliFx.Benchmarks
namespace CliFx.Benchmarks;
public partial class Benchmarks
{
public partial class Benchmarks
{
public class SystemCommandLineCommand
{
public static int ExecuteHandler(string s, int i, bool b) => 0;
@@ -40,5 +40,4 @@ namespace CliFx.Benchmarks
[Benchmark(Description = "System.CommandLine")]
public async Task<int> ExecuteWithSystemCommandLine() =>
await new SystemCommandLineCommand().ExecuteAsync(Arguments);
}
}

View File

@@ -3,18 +3,17 @@ using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Order;
using BenchmarkDotNet.Running;
namespace CliFx.Benchmarks
namespace CliFx.Benchmarks;
[RankColumn]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
public partial class 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>(
DefaultConfig
.Instance
.With(ConfigOptions.DisableOptimizationsValidator)
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
);
}
}

View File

@@ -2,15 +2,15 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.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="McMaster.Extensions.CommandLineUtils" Version="3.1.0" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.0.0" />
<PackageReference Include="PowerArgs" Version="3.6.0" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20574.7" />
</ItemGroup>

View File

@@ -2,12 +2,12 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -6,11 +6,11 @@ using CliFx.Demo.Utils;
using CliFx.Exceptions;
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.")]
public partial class BookAddCommand : ICommand
{
private readonly LibraryProvider _libraryProvider;
[CommandParameter(0, Name = "title", Description = "Book title.")]
@@ -43,10 +43,10 @@ namespace CliFx.Demo.Commands
return default;
}
}
}
public partial class BookAddCommand
{
public partial class BookAddCommand
{
private static readonly Random Random = new();
private static DateTimeOffset CreateRandomDate() => new(
@@ -66,5 +66,4 @@ namespace CliFx.Demo.Commands
Random.Next(0, 99),
Random.Next(0, 9)
);
}
}

View File

@@ -5,11 +5,11 @@ using CliFx.Demo.Utils;
using CliFx.Exceptions;
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.")]
public class BookCommand : ICommand
{
private readonly LibraryProvider _libraryProvider;
[CommandParameter(0, Name = "title", Description = "Title of the book to retrieve.")]
@@ -31,5 +31,4 @@ namespace CliFx.Demo.Commands
return default;
}
}
}

View File

@@ -4,11 +4,11 @@ using CliFx.Demo.Domain;
using CliFx.Demo.Utils;
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.")]
public class BookListCommand : ICommand
{
private readonly LibraryProvider _libraryProvider;
public BookListCommand(LibraryProvider libraryProvider)
@@ -33,5 +33,4 @@ namespace CliFx.Demo.Commands
return default;
}
}
}

View File

@@ -4,11 +4,11 @@ using CliFx.Demo.Domain;
using CliFx.Exceptions;
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.")]
public class BookRemoveCommand : ICommand
{
private readonly LibraryProvider _libraryProvider;
[CommandParameter(0, Name = "title", Description = "Title of the book to remove.")]
@@ -32,5 +32,4 @@ namespace CliFx.Demo.Commands
return default;
}
}
}

View File

@@ -1,9 +1,9 @@
using System;
namespace CliFx.Demo.Domain
namespace CliFx.Demo.Domain;
public class Book
{
public class Book
{
public string Title { get; }
public string Author { get; }
@@ -19,5 +19,4 @@ namespace CliFx.Demo.Domain
Published = published;
Isbn = isbn;
}
}
}

View File

@@ -1,9 +1,9 @@
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; }
@@ -25,10 +25,10 @@ namespace CliFx.Demo.Domain
public override string ToString() =>
$"{EanPrefix:000}-{RegistrationGroup:00}-{Registrant:00000}-{Publication:00}-{CheckDigit:0}";
}
}
public partial class Isbn
{
public partial class Isbn
{
public static Isbn Parse(string value, IFormatProvider formatProvider)
{
var components = value.Split('-', 5, StringSplitOptions.RemoveEmptyEntries);
@@ -41,5 +41,4 @@ namespace CliFx.Demo.Domain
int.Parse(components[4], formatProvider)
);
}
}
}

View File

@@ -2,10 +2,10 @@
using System.Collections.Generic;
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)
@@ -27,10 +27,9 @@ namespace CliFx.Demo.Domain
return new Library(books);
}
}
public partial class Library
{
public static Library Empty { get; } = new(Array.Empty<Book>());
}
}
public partial class Library
{
public static Library Empty { get; } = new(Array.Empty<Book>());
}

View File

@@ -2,10 +2,10 @@
using System.Linq;
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)
@@ -21,7 +21,7 @@ namespace CliFx.Demo.Domain
var data = File.ReadAllText(StorageFilePath);
return JsonConvert.DeserializeObject<Library>(data);
return JsonConvert.DeserializeObject<Library>(data) ?? Library.Empty;
}
public Book? TryGetBook(string title) => GetLibrary().Books.FirstOrDefault(b => b.Title == title);
@@ -37,5 +37,4 @@ namespace CliFx.Demo.Domain
var updatedLibrary = GetLibrary().WithoutBook(book);
StoreLibrary(updatedLibrary);
}
}
}

View File

@@ -1,36 +1,25 @@
using System;
using System.Threading.Tasks;
using CliFx;
using CliFx.Demo.Commands;
using CliFx.Demo.Domain;
using Microsoft.Extensions.DependencyInjection;
namespace CliFx.Demo
{
public static class Program
{
private static IServiceProvider GetServiceProvider()
{
// We use Microsoft.Extensions.DependencyInjection for injecting dependencies in commands
var services = new ServiceCollection();
// We use Microsoft.Extensions.DependencyInjection for injecting dependencies in commands
var services = new ServiceCollection();
// Register services
services.AddSingleton<LibraryProvider>();
// Register services
services.AddSingleton<LibraryProvider>();
// Register commands
services.AddTransient<BookCommand>();
services.AddTransient<BookAddCommand>();
services.AddTransient<BookRemoveCommand>();
services.AddTransient<BookListCommand>();
// Register commands
services.AddTransient<BookCommand>();
services.AddTransient<BookAddCommand>();
services.AddTransient<BookRemoveCommand>();
services.AddTransient<BookListCommand>();
return services.BuildServiceProvider();
}
var serviceProvider = services.BuildServiceProvider();
public static async Task<int> Main() =>
await new CliApplicationBuilder()
return await new CliApplicationBuilder()
.SetDescription("Demo application showcasing CliFx features.")
.AddCommandsFromThisAssembly()
.UseTypeActivator(GetServiceProvider().GetRequiredService)
.UseTypeActivator(serviceProvider.GetRequiredService)
.Build()
.RunAsync();
}
}

View File

@@ -2,10 +2,10 @@
using CliFx.Demo.Domain;
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)
{
// Title
@@ -33,5 +33,4 @@ namespace CliFx.Demo.Utils
using (writer.Console.WithForegroundColor(ConsoleColor.White))
writer.WriteLine(book.Isbn);
}
}
}

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>

View File

@@ -3,11 +3,11 @@ using System.Threading.Tasks;
using CliFx.Attributes;
using CliFx.Infrastructure;
namespace CliFx.Tests.Dummy.Commands
namespace CliFx.Tests.Dummy.Commands;
[Command("console-test")]
public class ConsoleTestCommand : ICommand
{
[Command("console-test")]
public class ConsoleTestCommand : ICommand
{
public ValueTask ExecuteAsync(IConsole console)
{
var input = console.Input.ReadToEnd();
@@ -20,5 +20,4 @@ namespace CliFx.Tests.Dummy.Commands
return default;
}
}
}

View File

@@ -2,11 +2,11 @@
using CliFx.Attributes;
using CliFx.Infrastructure;
namespace CliFx.Tests.Dummy.Commands
namespace CliFx.Tests.Dummy.Commands;
[Command("env-test")]
public class EnvironmentTestCommand : ICommand
{
[Command("env-test")]
public class EnvironmentTestCommand : ICommand
{
[CommandOption("target", EnvironmentVariable = "ENV_TARGET")]
public string GreetingTarget { get; set; } = "World";
@@ -16,5 +16,4 @@ namespace CliFx.Tests.Dummy.Commands
return default;
}
}
}

View File

@@ -1,24 +1,22 @@
using System.Reflection;
using System.Threading.Tasks;
namespace CliFx.Tests.Dummy
{
// This dummy application is used in tests for scenarios
// that require an external process to properly verify.
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
{
public static partial class Program
{
public static Assembly Assembly { get; } = typeof(Program).Assembly;
public static string Location { get; } = Assembly.Location;
}
}
public static partial class Program
{
public static partial class Program
{
public static async Task Main() =>
await new CliApplicationBuilder()
.AddCommandsFromThisAssembly()
.Build()
.RunAsync();
}
}

View File

@@ -6,10 +6,10 @@ using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
namespace CliFx.Tests
namespace CliFx.Tests;
public class ApplicationSpecs : SpecsBase
{
public class ApplicationSpecs : SpecsBase
{
public ApplicationSpecs(ITestOutputHelper testOutput)
: base(testOutput)
{
@@ -82,5 +82,4 @@ namespace CliFx.Tests
exitCode.Should().NotBe(0);
stdErr.Should().Contain("not a valid command");
}
}
}

View File

@@ -6,10 +6,10 @@ using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
namespace CliFx.Tests
namespace CliFx.Tests;
public class CancellationSpecs : SpecsBase
{
public class CancellationSpecs : SpecsBase
{
public CancellationSpecs(ITestOutputHelper testOutput)
: base(testOutput)
{
@@ -63,5 +63,4 @@ public class Command : ICommand
exitCode.Should().NotBe(0);
stdOut.Trim().Should().Be("Cancelled");
}
}
}

View File

@@ -1,9 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<TargetFramework>net6.0</TargetFramework>
<CollectCoverage>true</CollectCoverage>
<CoverletOutputFormat>opencover</CoverletOutputFormat>
</PropertyGroup>
@@ -13,14 +11,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.3.1" />
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Basic.Reference.Assemblies" Version="1.2.4" />
<PackageReference Include="CliWrap" Version="3.4.0" />
<PackageReference Include="FluentAssertions" Version="6.3.0" />
<PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<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>

View File

@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using CliFx.Infrastructure;
using CliFx.Tests.Utils;
using CliWrap;
using CliWrap.Buffered;
@@ -8,10 +11,10 @@ using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
namespace CliFx.Tests
namespace CliFx.Tests;
public class ConsoleSpecs : SpecsBase
{
public class ConsoleSpecs : SpecsBase
{
public ConsoleSpecs(ITestOutputHelper testOutput)
: base(testOutput)
{
@@ -77,9 +80,9 @@ public class Command : ICommand
// Assert
exitCode.Should().Be(0);
Console.OpenStandardInput().Should().NotBe(FakeConsole.Input.BaseStream);
Console.OpenStandardOutput().Should().NotBe(FakeConsole.Output.BaseStream);
Console.OpenStandardError().Should().NotBe(FakeConsole.Error.BaseStream);
Console.OpenStandardInput().Should().NotBeSameAs(FakeConsole.Input.BaseStream);
Console.OpenStandardOutput().Should().NotBeSameAs(FakeConsole.Output.BaseStream);
Console.OpenStandardError().Should().NotBeSameAs(FakeConsole.Error.BaseStream);
Console.ForegroundColor.Should().NotBe(ConsoleColor.DarkMagenta);
Console.BackgroundColor.Should().NotBe(ConsoleColor.DarkMagenta);
@@ -135,5 +138,21 @@ public class Command : ICommand
stdOut.Trim().Should().Be("Hello world");
stdErr.Trim().Should().Be("Hello world");
}
[Fact]
public void Console_does_not_emit_preamble_when_used_with_encoding_that_has_it()
{
// Arrange
using var buffer = new MemoryStream();
using var consoleWriter = new ConsoleWriter(FakeConsole, buffer, Encoding.UTF8);
// Act
consoleWriter.Write("Hello world");
consoleWriter.Flush();
var output = consoleWriter.Encoding.GetString(buffer.ToArray());
// Assert
output.Should().Be("Hello world");
}
}

View File

@@ -6,10 +6,10 @@ using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
namespace CliFx.Tests
namespace CliFx.Tests;
public class ConversionSpecs : SpecsBase
{
public class ConversionSpecs : SpecsBase
{
public ConversionSpecs(ITestOutputHelper testOutput)
: base(testOutput)
{
@@ -946,5 +946,47 @@ public class Command : ICommand
exitCode.Should().NotBe(0);
stdErr.Should().Contain("Hello world");
}
[Fact]
public async Task Parameter_or_option_value_conversion_fails_if_the_static_parse_method_throws()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
@"
public class CustomType
{
public string Value { get; }
private CustomType(string value) => Value = value;
public static CustomType Parse(string value) => throw new Exception(""Hello world"");
}
[Command]
public class Command : ICommand
{
[CommandOption('f')]
public CustomType Foo { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
");
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"-f", "bar"},
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
stdErr.Should().Contain("Hello world");
}
}

View File

@@ -10,10 +10,10 @@ using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
namespace CliFx.Tests
namespace CliFx.Tests;
public class DirectivesSpecs : SpecsBase
{
public class DirectivesSpecs : SpecsBase
{
public DirectivesSpecs(ITestOutputHelper testOutput)
: base(testOutput)
{
@@ -108,5 +108,4 @@ public class Command : ICommand
"ENV_KIL", "=", "\"world\""
);
}
}
}

View File

@@ -10,10 +10,10 @@ using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
namespace CliFx.Tests
namespace CliFx.Tests;
public class EnvironmentSpecs : SpecsBase
{
public class EnvironmentSpecs : SpecsBase
{
public EnvironmentSpecs(ITestOutputHelper testOutput)
: base(testOutput)
{
@@ -256,5 +256,4 @@ public class Command : ICommand
// Assert
result.StandardOutput.Trim().Should().Be("Hello Mars!");
}
}
}

View File

@@ -7,10 +7,10 @@ using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
namespace CliFx.Tests
namespace CliFx.Tests;
public class ErrorReportingSpecs : SpecsBase
{
public class ErrorReportingSpecs : SpecsBase
{
public ErrorReportingSpecs(ITestOutputHelper testOutput)
: base(testOutput)
{
@@ -201,5 +201,4 @@ public class Command : ICommand
stdOut.Should().Contain("This will be in help text");
stdErr.Trim().Should().Be("Something went wrong");
}
}
}

View File

@@ -7,10 +7,10 @@ using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
namespace CliFx.Tests
namespace CliFx.Tests;
public class HelpTextSpecs : SpecsBase
{
public class HelpTextSpecs : SpecsBase
{
public HelpTextSpecs(ITestOutputHelper testOutput)
: base(testOutput)
{
@@ -371,6 +371,57 @@ public class Command : ICommand
);
}
// https://github.com/Tyrrrz/CliFx/issues/117
[Fact]
public async Task Help_text_shows_usage_format_which_lists_all_parameters_in_specified_order()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
@"
// Base members appear last in reflection order
public abstract class CommandBase : ICommand
{
[CommandParameter(0)]
public string Foo { get; set; }
public abstract ValueTask ExecuteAsync(IConsole console);
}
[Command]
public class Command : CommandBase
{
[CommandParameter(2)]
public IReadOnlyList<string> Baz { get; set; }
[CommandParameter(1)]
public string Bar { get; set; }
public override ValueTask ExecuteAsync(IConsole console) => default;
}
");
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] { "--help" },
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
stdOut.Should().ContainAllInOrder(
"USAGE",
"<foo>", "<bar>", "<baz...>"
);
}
[Fact]
public async Task Help_text_shows_usage_format_which_lists_all_required_options()
{
@@ -590,10 +641,55 @@ public enum CustomEnum { One, Two, Three }
public class Command : ICommand
{
[CommandParameter(0)]
public List<CustomEnum> Foo { get; set; }
public IReadOnlyList<CustomEnum> Foo { get; set; }
[CommandOption(""bar"")]
public List<CustomEnum> Bar { get; set; }
public IReadOnlyList<CustomEnum> Bar { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
");
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"--help"},
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
stdOut.Should().ContainAllInOrder(
"PARAMETERS",
"foo", "Choices:", "One", "Two", "Three",
"OPTIONS",
"--bar", "Choices:", "One", "Two", "Three"
);
}
[Fact]
public async Task Help_text_shows_all_valid_values_for_nullable_enum_parameters_and_options()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
@"
public enum CustomEnum { One, Two, Three }
[Command]
public class Command : ICommand
{
[CommandParameter(0)]
public CustomEnum? Foo { get; set; }
[CommandOption(""bar"")]
public IReadOnlyList<CustomEnum?> Bar { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
@@ -919,5 +1015,4 @@ public class SecondCommandSecondChildCommand : ICommand
exitCode.Should().Be(0);
stdOut.Trim().Should().Be("v6.9");
}
}
}

View File

@@ -7,10 +7,10 @@ using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
namespace CliFx.Tests
namespace CliFx.Tests;
public class OptionBindingSpecs : SpecsBase
{
public class OptionBindingSpecs : SpecsBase
{
public OptionBindingSpecs(ITestOutputHelper testOutput)
: base(testOutput)
{
@@ -496,6 +496,83 @@ public class Command : ICommand
);
}
[Fact]
public async Task Option_binding_supports_multiple_inheritance_through_default_interface_members()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
@"
public static class SharedContext
{
public static int Foo { get; set; }
public static bool Bar { get; set; }
}
public interface IHasFoo : ICommand
{
[CommandOption(""foo"")]
public int Foo
{
get => SharedContext.Foo;
set => SharedContext.Foo = value;
}
}
public interface IHasBar : ICommand
{
[CommandOption(""bar"")]
public bool Bar
{
get => SharedContext.Bar;
set => SharedContext.Bar = value;
}
}
public interface IHasBaz : ICommand
{
public string Baz { get; set; }
}
[Command]
public class Command : IHasFoo, IHasBar, IHasBaz
{
[CommandOption(""baz"")]
public string Baz { get; set; }
public ValueTask ExecuteAsync(IConsole console)
{
console.Output.WriteLine(""Foo = "" + SharedContext.Foo);
console.Output.WriteLine(""Bar = "" + SharedContext.Bar);
console.Output.WriteLine(""Baz = "" + Baz);
return default;
}
}
");
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] { "--foo", "42", "--bar", "--baz", "xyz" }
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
stdOut.Should().ConsistOfLines(
"Foo = 42",
"Bar = True",
"Baz = xyz"
);
}
[Fact]
public async Task Option_binding_does_not_consider_a_negative_number_as_an_option_name_or_short_name()
{
@@ -704,5 +781,4 @@ public class Command : ICommand
exitCode.Should().NotBe(0);
stdErr.Should().Contain("expects a single argument, but provided with multiple");
}
}
}

View File

@@ -6,10 +6,10 @@ using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
namespace CliFx.Tests
namespace CliFx.Tests;
public class ParameterBindingSpecs : SpecsBase
{
public class ParameterBindingSpecs : SpecsBase
{
public ParameterBindingSpecs(ITestOutputHelper testOutput)
: base(testOutput)
{
@@ -120,7 +120,53 @@ public class Command : ICommand
}
[Fact]
public async Task Parameter_binding_fails_if_one_of_the_parameters_has_not_been_provided()
public async Task Parameter_is_not_bound_if_there_are_no_arguments_matching_its_order()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
@"
[Command]
public class Command : ICommand
{
[CommandParameter(0)]
public string Foo { get; set; }
[CommandParameter(1, IsRequired = false)]
public string Bar { get; set; } = ""xyz"";
public ValueTask ExecuteAsync(IConsole console)
{
console.Output.WriteLine(""Foo = "" + Foo);
console.Output.WriteLine(""Bar = "" + Bar);
return default;
}
}");
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"abc"},
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
stdOut.Should().ConsistOfLines(
"Foo = abc",
"Bar = xyz"
);
}
[Fact]
public async Task Parameter_binding_fails_if_a_required_parameter_has_not_been_provided()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
@@ -153,7 +199,7 @@ public class Command : ICommand
// Assert
exitCode.Should().NotBe(0);
stdErr.Should().Contain("Missing parameter(s)");
stdErr.Should().Contain("Missing required parameter(s)");
}
[Fact]
@@ -190,7 +236,7 @@ public class Command : ICommand
// Assert
exitCode.Should().NotBe(0);
stdErr.Should().Contain("Missing parameter(s)");
stdErr.Should().Contain("Missing required parameter(s)");
}
[Fact]
@@ -229,5 +275,4 @@ public class Command : ICommand
exitCode.Should().NotBe(0);
stdErr.Should().Contain("Unexpected parameter(s)");
}
}
}

View File

@@ -6,10 +6,10 @@ using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
namespace CliFx.Tests
namespace CliFx.Tests;
public class RoutingSpecs : SpecsBase
{
public class RoutingSpecs : SpecsBase
{
public RoutingSpecs(ITestOutputHelper testOutput)
: base(testOutput)
{
@@ -182,5 +182,4 @@ public class NamedChildCommand : ICommand
exitCode.Should().Be(0);
stdOut.Trim().Should().Be("cmd child");
}
}
}

View File

@@ -3,10 +3,10 @@ using CliFx.Infrastructure;
using CliFx.Tests.Utils.Extensions;
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();
@@ -19,5 +19,4 @@ namespace CliFx.Tests
FakeConsole.DumpToTestOutput(TestOutput);
FakeConsole.Dispose();
}
}
}

View File

@@ -7,10 +7,10 @@ using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
namespace CliFx.Tests
namespace CliFx.Tests;
public class TypeActivationSpecs : SpecsBase
{
public class TypeActivationSpecs : SpecsBase
{
public TypeActivationSpecs(ITestOutputHelper testOutput)
: base(testOutput)
{
@@ -161,5 +161,4 @@ public class Command : ICommand
exitCode.Should().NotBe(0);
stdErr.Should().Contain("Failed to create an instance of type");
}
}
}

View File

@@ -3,24 +3,25 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Basic.Reference.Assemblies;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
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.
//
// 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
@@ -60,16 +61,9 @@ namespace CliFx.Tests.Utils
var compilation = CSharpCompilation.Create(
"CliFxTests_DynamicAssembly_" + Guid.NewGuid(),
new[] {ast},
new[]
{
MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location),
MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location),
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location),
MetadataReference.CreateFromFile(typeof(DynamicCommandBuilder).Assembly.Location),
MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location)
},
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)
);
@@ -112,13 +106,13 @@ namespace CliFx.Tests.Utils
// Return all defined commands
var commandTypes = generatedAssembly
.GetTypes()
.Where(t => t.IsAssignableTo(typeof(ICommand)))
.Where(t => t.IsAssignableTo(typeof(ICommand)) && !t.IsAbstract)
.ToArray();
if (commandTypes.Length <= 0)
{
throw new InvalidOperationException(
"There are no command definitions in the provide source code."
"There are no command definitions in the provided source code."
);
}
@@ -132,11 +126,10 @@ namespace CliFx.Tests.Utils
if (commandTypes.Count > 1)
{
throw new InvalidOperationException(
"There are more than one command definitions in the provide source code."
"There are more than one command definitions in the provided source code."
);
}
return commandTypes.Single();
}
}
}

View File

@@ -1,24 +1,22 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using FluentAssertions.Collections;
using FluentAssertions.Execution;
using FluentAssertions.Primitives;
namespace CliFx.Tests.Utils.Extensions
namespace CliFx.Tests.Utils.Extensions;
internal static class AssertionExtensions
{
internal static class AssertionExtensions
{
public static AndConstraint<StringCollectionAssertions> ConsistOfLines(
public static void ConsistOfLines(
this StringAssertions assertions,
IEnumerable<string> lines)
{
var actualLines = assertions.Subject.Split(new[] {'\n', '\r'}, StringSplitOptions.RemoveEmptyEntries);
return actualLines.Should().Equal(lines);
actualLines.Should().Equal(lines);
}
public static AndConstraint<StringCollectionAssertions> ConsistOfLines(
public static void ConsistOfLines(
this StringAssertions assertions,
params string[] lines) =>
assertions.ConsistOfLines((IEnumerable<string>) lines);
@@ -50,5 +48,4 @@ namespace CliFx.Tests.Utils.Extensions
this StringAssertions assertions,
params string[] values) =>
assertions.ContainAllInOrder((IEnumerable<string>) values);
}
}

View File

@@ -1,10 +1,10 @@
using CliFx.Infrastructure;
using Xunit.Abstractions;
namespace CliFx.Tests.Utils.Extensions
namespace CliFx.Tests.Utils.Extensions;
internal static class ConsoleExtensions
{
internal static class ConsoleExtensions
{
public static void DumpToTestOutput(this FakeInMemoryConsole console, ITestOutputHelper testOutputHelper)
{
testOutputHelper.WriteLine("[*] Captured standard output:");
@@ -13,5 +13,4 @@ namespace CliFx.Tests.Utils.Extensions
testOutputHelper.WriteLine("[*] Captured standard error:");
testOutputHelper.WriteLine(console.ReadErrorString());
}
}
}

View File

@@ -2,11 +2,10 @@
using CliFx.Attributes;
using CliFx.Infrastructure;
namespace CliFx.Tests.Utils
namespace CliFx.Tests.Utils;
[Command]
public class NoOpCommand : ICommand
{
[Command]
public class NoOpCommand : ICommand
{
public ValueTask ExecuteAsync(IConsole console) => default;
}
}

Some files were not shown because too many files have changed in this diff Show More