Skip to content

Commit

Permalink
Separate parser signature analysis and options type analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
DoctorKrolic committed Mar 15, 2024
1 parent d7826e8 commit 0e93606
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 166 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,80 @@

namespace ArgumentParsing.Generators.Diagnostics.Analyzers;

public partial class ArgumentParserAnalyzer
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class OptionsTypeAnalyzer : DiagnosticAnalyzer
{
private static void AnalyzeOptionsType(SymbolAnalysisContext context, INamedTypeSymbol optionsType, KnownTypes knownTypes)
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
ImmutableArray.Create(
DiagnosticDescriptors.RequiredFieldInOptionsTypeIsNotAllowed,
DiagnosticDescriptors.RequiredPropertyMustParticipateInArgumentParsing,
DiagnosticDescriptors.PropertyIsNotAccessible,
DiagnosticDescriptors.PropertyMustHaveAccessibleSetter,
// ARGP0011
DiagnosticDescriptors.InvalidShortName,
DiagnosticDescriptors.InvalidLongName,
DiagnosticDescriptors.DuplicateShortName,
DiagnosticDescriptors.DuplicateLongName,
DiagnosticDescriptors.InvalidOptionPropertyType,
// ARGP0017
// ARGP0018
DiagnosticDescriptors.RequiredBoolOption,
DiagnosticDescriptors.RequiredNullableOption,
// ARGP0021
DiagnosticDescriptors.NegativeParameterIndex,
DiagnosticDescriptors.DuplicateParameterIndex,
DiagnosticDescriptors.InvalidParameterPropertyType,
DiagnosticDescriptors.MissingParameterWithIndex,
DiagnosticDescriptors.MissingParametersWithIndexes,
DiagnosticDescriptors.RequiredCanOnlyBeFirstNParametersInARow,
DiagnosticDescriptors.InvalidParameterName,
DiagnosticDescriptors.DuplicateRemainingParameters,
DiagnosticDescriptors.InvalidRemainingParametersPropertyType,
DiagnosticDescriptors.TooLowAccessibilityOfOptionsType,
DiagnosticDescriptors.NoOptionNames);

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);

context.RegisterCompilationStartAction(static context =>
{
var comp = context.Compilation;
var knownTypes = new KnownTypes
{
OptionsTypeAttributeType = comp.GetTypeByMetadataName("ArgumentParsing.OptionsTypeAttribute")!,
OptionAttributeType = comp.GetTypeByMetadataName("ArgumentParsing.OptionAttribute")!,
ParameterAttributeType = comp.GetTypeByMetadataName("ArgumentParsing.ParameterAttribute")!,
RemainingParametersAttributeType = comp.GetTypeByMetadataName("ArgumentParsing.RemainingParametersAttribute")!,
RequiredAttributeType = comp.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.RequiredAttribute")!,
IEnumerableOfTType = comp.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T),
IReadOnlyCollectionOfTType = comp.GetSpecialType(SpecialType.System_Collections_Generic_IReadOnlyCollection_T),
IReadOnlyListOfTType = comp.GetSpecialType(SpecialType.System_Collections_Generic_IReadOnlyList_T),
ImmutableArrayOfTType = comp.GetTypeByMetadataName("System.Collections.Immutable.ImmutableArray`1"),
};

context.RegisterSymbolAction(context => AnalyzeOptionsType(context, knownTypes), SymbolKind.NamedType);
});
}

private static void AnalyzeOptionsType(SymbolAnalysisContext context, KnownTypes knownTypes)
{
var optionsType = (INamedTypeSymbol)context.Symbol;

if (!optionsType.GetAttributes()
.Any(a => a.AttributeClass?.Equals(knownTypes.OptionsTypeAttributeType, SymbolEqualityComparer.Default) == true))
{
return;
}

if (optionsType.DeclaredAccessibility < Accessibility.Internal)
{
context.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.TooLowAccessibilityOfOptionsType, optionsType.Locations.First()));
}

var seenShortNames = new HashSet<char>();
var seenLongNames = new HashSet<string>();

Expand Down Expand Up @@ -487,4 +557,25 @@ property.SetMethod is not null &&
return (namedType.GetPrimaryParseStrategy(), isNullable, isSequence);
}
}

private readonly struct KnownTypes
{
public required INamedTypeSymbol OptionsTypeAttributeType { get; init; }

public required INamedTypeSymbol OptionAttributeType { get; init; }

public required INamedTypeSymbol ParameterAttributeType { get; init; }

public required INamedTypeSymbol RemainingParametersAttributeType { get; init; }

public required INamedTypeSymbol RequiredAttributeType { get; init; }

public required INamedTypeSymbol IEnumerableOfTType { get; init; }

public required INamedTypeSymbol IReadOnlyCollectionOfTType { get; init; }

public required INamedTypeSymbol IReadOnlyListOfTType { get; init; }

public required INamedTypeSymbol? ImmutableArrayOfTType { get; init; }
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,43 @@
using System.Collections.Immutable;
using ArgumentParsing.Generators.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace ArgumentParsing.Generators.Diagnostics.Analyzers;

public partial class ArgumentParserAnalyzer
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ParserSignatureAnalyzer : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
ImmutableArray.Create(
DiagnosticDescriptors.InvalidParserParameterCount,
DiagnosticDescriptors.InvalidArgsParameter,
DiagnosticDescriptors.InvalidArgsParameterType,
DiagnosticDescriptors.PreferArgsParameterName,
DiagnosticDescriptors.ReturnTypeMustBeParseResult,
DiagnosticDescriptors.InvalidOptionsType,
DiagnosticDescriptors.OptionsTypeMustBeAnnotatedWithAttribute);

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);

context.RegisterCompilationStartAction(static context =>
{
var comp = context.Compilation;
var knownTypes = new KnownTypes
{
GeneratedArgumentParserAttributeType = comp.GetTypeByMetadataName("ArgumentParsing.GeneratedArgumentParserAttribute")!,
ParseResultOfTType = comp.GetTypeByMetadataName("ArgumentParsing.Results.ParseResult`1")!,
OptionsTypeAttributeType = comp.GetTypeByMetadataName("ArgumentParsing.OptionsTypeAttribute")!,
};

context.RegisterSymbolAction(context => AnalyzeParserSignature(context, knownTypes), SymbolKind.Method);
});
}

private static void AnalyzeParserSignature(SymbolAnalysisContext context, KnownTypes knownTypes)
{
var method = (IMethodSymbol)context.Symbol;
Expand Down Expand Up @@ -92,17 +123,15 @@ private static void AnalyzeParserSignature(SymbolAnalysisContext context, KnownT
Diagnostic.Create(
DiagnosticDescriptors.OptionsTypeMustBeAnnotatedWithAttribute, genericArgumentErrorSyntax.GetLocation()));
}
else
{
if (namedOptionsType.DeclaredAccessibility < Accessibility.Internal)
{
context.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.TooLowAccessibilityOfOptionsType, namedOptionsType.Locations.First()));
}

AnalyzeOptionsType(context, namedOptionsType, knownTypes);
}
}
}

private readonly struct KnownTypes
{
public required INamedTypeSymbol GeneratedArgumentParserAttributeType { get; init; }

public required INamedTypeSymbol ParseResultOfTType { get; init; }

public required INamedTypeSymbol OptionsTypeAttributeType { get; init; }
}
}
Loading

0 comments on commit 0e93606

Please sign in to comment.