Skip to content

Commit

Permalink
Required options type to be annotated with [OptionsType] attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
DoctorKrolic committed Mar 15, 2024
1 parent 9dfcc1c commit d0c6d8f
Show file tree
Hide file tree
Showing 38 changed files with 238 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ partial class Program
}
}

[OptionsType]
class Options
{
[Option('v'), HelpInfo("Enables verbose logging")]
Expand Down
6 changes: 5 additions & 1 deletion src/ArgumentParsing.Generators/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
ARGP0034 | ArgumentParsing | Error |
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ private static (ArgumentParserInfo? ArgumentParserInfo, OptionsHelpInfo? Options
return default;
}

if (optionsType is not INamedTypeSymbol { SpecialType: SpecialType.None, TypeKind: TypeKind.Class or TypeKind.Struct } namedOptionsType || !namedOptionsType.Constructors.Any(c => c.Parameters.Length == 0))
var optionsTypeAttributeType = compilation.GetTypeByMetadataName("ArgumentParsing.OptionsTypeAttribute")!;

if (optionsType is not INamedTypeSymbol { SpecialType: SpecialType.None, TypeKind: TypeKind.Class or TypeKind.Struct } namedOptionsType ||
!namedOptionsType.Constructors.Any(c => c.Parameters.Length == 0) ||
!namedOptionsType.GetAttributes().Any(a => a.AttributeClass?.Equals(optionsTypeAttributeType, SymbolEqualityComparer.Default) == true))
{
return default;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ private static void AnalyzeParserSignature(SymbolAnalysisContext context, KnownT
if (returnType.TypeKind != TypeKind.Error)
{
var returnTypeSyntax = ((MethodDeclarationSyntax)method.DeclaringSyntaxReferences.First().GetSyntax(context.CancellationToken)).ReturnType;
var genericArgumentErrorSyntax = returnTypeSyntax is GenericNameSyntax { TypeArgumentList.Arguments: [var genericArgument] } ? genericArgument : returnTypeSyntax;

if (returnType is not INamedTypeSymbol { TypeArguments: [var optionsType] } namedReturnType ||
!namedReturnType.ConstructedFrom.Equals(knownTypes.ParseResultOfTType, SymbolEqualityComparer.Default))
Expand All @@ -80,13 +81,17 @@ private static void AnalyzeParserSignature(SymbolAnalysisContext context, KnownT
{
if (optionsType.TypeKind != TypeKind.Error)
{
var errorNode = returnTypeSyntax is GenericNameSyntax { TypeArgumentList.Arguments: [var genericArgument] } ? genericArgument : returnTypeSyntax;

context.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.InvalidOptionsType, errorNode.GetLocation()));
DiagnosticDescriptors.InvalidOptionsType, genericArgumentErrorSyntax.GetLocation()));
}
}
else if (!namedOptionsType.GetAttributes().Any(a => a.AttributeClass?.Equals(knownTypes.OptionsTypeAttributeType, SymbolEqualityComparer.Default) == true))
{
context.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.OptionsTypeMustBeAnnotatedWithAttribute, genericArgumentErrorSyntax.GetLocation()));
}
else
{
if (namedOptionsType.DeclaredAccessibility < Accessibility.Internal)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public sealed partial class ArgumentParserAnalyzer : DiagnosticAnalyzer
DiagnosticDescriptors.InvalidRemainingParametersPropertyType,
// ARGP0031
DiagnosticDescriptors.TooLowAccessibilityOfOptionsType,
DiagnosticDescriptors.NoOptionNames);
DiagnosticDescriptors.NoOptionNames,
DiagnosticDescriptors.OptionsTypeMustBeAnnotatedWithAttribute);

public override void Initialize(AnalysisContext context)
{
Expand All @@ -55,6 +56,7 @@ public override void Initialize(AnalysisContext context)
{
GeneratedArgumentParserAttributeType = comp.GetTypeByMetadataName("ArgumentParsing.GeneratedArgumentParserAttribute")!,
ParseResultOfTType = comp.GetTypeByMetadataName("ArgumentParsing.Results.ParseResult`1")!,
OptionsTypeAttributeType = comp.GetTypeByMetadataName("ArgumentParsing.OptionsTypeAttribute")!,
OptionAttributeType = comp.GetTypeByMetadataName("ArgumentParsing.OptionAttribute")!,
ParameterAttributeType = comp.GetTypeByMetadataName("ArgumentParsing.ParameterAttribute")!,
RemainingParametersAttributeType = comp.GetTypeByMetadataName("ArgumentParsing.RemainingParametersAttribute")!,
Expand All @@ -75,6 +77,8 @@ private readonly struct KnownTypes

public required INamedTypeSymbol ParseResultOfTType { get; init; }

public required INamedTypeSymbol OptionsTypeAttributeType { get; init; }

public required INamedTypeSymbol OptionAttributeType { get; init; }

public required INamedTypeSymbol ParameterAttributeType { get; init; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,4 +244,12 @@ public static class DiagnosticDescriptors
category: ArgumentParsingCategoryName,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor OptionsTypeMustBeAnnotatedWithAttribute = new(
id: "ARGP0034",
title: "Option type must be annotated with [OptionsType] attribute",
messageFormat: "Option type must be annotated with [OptionsType] attribute",
category: ArgumentParsingCategoryName,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);
}
9 changes: 9 additions & 0 deletions src/ArgumentParsing/OptionsTypeAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace ArgumentParsing;

/// <summary>
/// Indicates an options type, i.e. type, parsed arguments are mapped to
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public sealed class OptionsTypeAttribute : Attribute
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class MixedFlagOptionsAndParametersTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Option('f', "flag")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class MixedFlagsOptionsTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Option('f')]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class MixedNullableOptionsTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Option('b')]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class MixedNullableParametersTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Parameter(0)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class MixedSequenceOptionsTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Option('s')]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class MixedStringOptionsAndParametersTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Option('o')]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SampleSumAppTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Option('v')]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleCharOptionsTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Option('c')]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleCharParametersTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Parameter(0)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleCharRemainingParametersTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Parameter(0)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ private enum MyEnum
EnumValue1, EnumValue2
}

[OptionsType]
private sealed class Options
{
[Option('e')]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleEnumParametersTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Parameter(0)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleEnumRemainingParametersTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Parameter(0)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleFlagOptionsTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Option('a')]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleFlagParametersTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Parameter(0)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleFlagRemainingParametersTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Parameter(0)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleHelpCommandTest
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Option('o'), HelpInfo("Option help description")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleIntegerRemainingParametersTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Parameter(0)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleNullableFlagOptionsTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Option('a', "flag-a")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleNumericOptionsTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Option('i')]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleNumericParametersTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Parameter(0)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleRequiredOptionsTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Option('r')]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleRequiredParametersTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Parameter(0)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleStringOptionsTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Option('a')]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleStringParametersTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Parameter(0)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleStringRemainingParametersTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
[Parameter(0)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace ArgumentParsing.Tests.Functional;
public sealed partial class SimpleVersionCommandTests
{
#region OptionsAndParser
[OptionsType]
private sealed class Options
{
}
Expand Down
Loading

0 comments on commit d0c6d8f

Please sign in to comment.