Skip to content

Commit

Permalink
Move reporting of parameter-related diagnostics to an analyzer
Browse files Browse the repository at this point in the history
  • Loading branch information
DoctorKrolic committed Mar 11, 2024
1 parent 2fa5aed commit 1b19a39
Show file tree
Hide file tree
Showing 6 changed files with 669 additions and 120 deletions.
42 changes: 3 additions & 39 deletions src/ArgumentParsing.Generators/ArgumentParserGenerator.Extract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ private static (OptionsInfo? OptionsInfo, OptionsHelpInfo? OptionsHelpInfo, Immu
var parameterMap = new Dictionary<int, ParameterInfo>();
var parameterHelpDescriptionsMap = new Dictionary<ParameterInfo, string?>();
var parametersProperties = new Dictionary<ParameterInfo, IPropertySymbol>();
var firstIndexWithNoError = new Dictionary<int, IPropertySymbol>();

var declaredRemainingParameters = false;
RemainingParametersInfo? remainingParametersInfo = null;
Expand Down Expand Up @@ -344,48 +343,23 @@ private static (OptionsInfo? OptionsInfo, OptionsHelpInfo? OptionsHelpInfo, Immu
if (parameterIndex < 0)
{
hasErrors = true;
diagnosticsBuilder.Add(DiagnosticInfo.Create(DiagnosticDescriptors.NegativeParameterIndex, property));
}
else
else if (hasParameter)
{
if (hasParameter)
{
hasErrors = true;

if (firstIndexWithNoError.TryGetValue(parameterIndex, out var previousProperty))
{
diagnosticsBuilder.Add(DiagnosticInfo.Create(DiagnosticDescriptors.DuplicateParameterIndex, previousProperty, parameterIndex));
firstIndexWithNoError.Remove(parameterIndex);
}

diagnosticsBuilder.Add(DiagnosticInfo.Create(DiagnosticDescriptors.DuplicateParameterIndex, property, parameterIndex));
}
else
{
firstIndexWithNoError.Add(parameterIndex, property);
}
hasErrors = true;
}

parameterName ??= propertyName.ToKebabCase();

if (!char.IsLetter(parameterName[0]) || !parameterName.Replace("-", string.Empty).All(char.IsLetterOrDigit))
{
hasErrors = true;
diagnosticsBuilder.Add(DiagnosticInfo.Create(DiagnosticDescriptors.InvalidParameterName, property, parameterName));
}

var (parseStrategy, nullableUnderlyingType) = GetParseStrategyForParameter(propertyType);
if (parseStrategy == ParseStrategy.None)
{
hasErrors = true;

if (propertyType.TypeKind != TypeKind.Error)
{
var propertySyntax = (BasePropertyDeclarationSyntax?)property.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken);
var diagnosticLocation = propertySyntax?.Type.GetLocation() ?? property.Locations.First();

diagnosticsBuilder.Add(DiagnosticInfo.Create(DiagnosticDescriptors.InvalidParameterPropertyType, diagnosticLocation, propertyType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
}
}

if (!hasParameter)
Expand Down Expand Up @@ -461,15 +435,6 @@ private static (OptionsInfo? OptionsInfo, OptionsHelpInfo? OptionsHelpInfo, Immu
if (index > (lastSeenIndex + 1))
{
hasErrors = true;

if (index - lastSeenIndex == 2)
{
diagnosticsBuilder.Add(DiagnosticInfo.Create(DiagnosticDescriptors.MissingParameterWithIndex, optionsType, index - 1));
}
else
{
diagnosticsBuilder.Add(DiagnosticInfo.Create(DiagnosticDescriptors.MissingParametersWithIndexes, optionsType, lastSeenIndex + 1, index - 1));
}
}

var parameterInfo = pair.Value;
Expand All @@ -487,7 +452,6 @@ private static (OptionsInfo? OptionsInfo, OptionsHelpInfo? OptionsHelpInfo, Immu
if (!canNextParameterBeRequired)
{
hasErrors = true;
diagnosticsBuilder.Add(DiagnosticInfo.Create(DiagnosticDescriptors.RequiredCanOnlyBeFirstNParametersInARow, parametersProperties[info]));
}
}
else
Expand Down Expand Up @@ -517,7 +481,7 @@ private static (OptionsInfo? OptionsInfo, OptionsHelpInfo? OptionsHelpInfo, Immu

return (optionsInfo, optionsHelpInfo, diagnosticsBuilder.ToImmutable());

static (ParseStrategy, string? NullableUnderlyingType, SequenceType SequenceType, string? SequenceUnderlyingType) GetParseStrategyForOption(ITypeSymbol type, Compilation compilation)
static (ParseStrategy, string? NullableUnderlyingType, SequenceType, string? SequenceUnderlyingType) GetParseStrategyForOption(ITypeSymbol type, Compilation compilation)
{
string? nullableUnderlyingType = null;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Immutable;
using ArgumentParsing.Generators.Extensions;
using ArgumentParsing.Generators.Models;
using Microsoft.CodeAnalysis;
Expand All @@ -16,6 +17,10 @@ private static void AnalyzeOptionsType(SymbolAnalysisContext context, INamedType
var firstPropertyOfShortNameWithNoError = new Dictionary<char, IPropertySymbol>();
var firstPropertyOfLongNameWithNoError = new Dictionary<string, IPropertySymbol>();

var seenParametersWithTheirRequirements = new Dictionary<int, bool>();
var firstPropertyOfParameterIndexWithNoError = new Dictionary<int, IPropertySymbol>();
var parametersProperties = new Dictionary<int, IPropertySymbol>();

foreach (var member in optionsType.GetMembers())
{
context.CancellationToken.ThrowIfCancellationRequested();
Expand Down Expand Up @@ -122,6 +127,7 @@ private static void AnalyzeOptionsType(SymbolAnalysisContext context, INamedType
}
}

var propertyType = property.Type;
var propertyLocation = property.Locations.First();

if (!isOption && !isParameter && !isRemainingParameters)
Expand Down Expand Up @@ -233,8 +239,7 @@ property.SetMethod is not null &&
}
}

var propertyType = property.Type;
var (parseStrategy, isNullable) = GetParseStrategy(propertyType, knownTypes);
var (parseStrategy, isNullable, _) = GetParseStrategy(propertyType, knownTypes);

if (parseStrategy == ParseStrategy.None)
{
Expand Down Expand Up @@ -266,16 +271,135 @@ property.SetMethod is not null &&
DiagnosticDescriptors.RequiredNullableOption, propertyLocation));
}
}
else if (isParameter)
{
var hasParameter = seenParametersWithTheirRequirements.ContainsKey(parameterIndex);

if (parameterIndex < 0)
{
context.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.NegativeParameterIndex, propertyLocation));
}
else
{
if (hasParameter)
{
if (firstPropertyOfParameterIndexWithNoError.TryGetValue(parameterIndex, out var previousProperty))
{
context.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.DuplicateParameterIndex,
previousProperty.Locations.First(),
parameterIndex.ToString()));

firstPropertyOfParameterIndexWithNoError.Remove(parameterIndex);
}

context.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.DuplicateParameterIndex,
propertyLocation,
parameterIndex.ToString()));
}
else
{
firstPropertyOfParameterIndexWithNoError.Add(parameterIndex, property);
}
}

parameterName ??= property.Name.ToKebabCase();

if (!char.IsLetter(parameterName[0]) || !parameterName.Replace("-", string.Empty).All(char.IsLetterOrDigit))
{
context.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.InvalidParameterName, propertyLocation, parameterName));
}

var (parseStrategy, isNullable, isSequence) = GetParseStrategy(propertyType, knownTypes);
if ((parseStrategy == ParseStrategy.None || isSequence) && propertyType.TypeKind != TypeKind.Error)
{
var propertySyntax = (BasePropertyDeclarationSyntax?)property.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(context.CancellationToken);

context.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.InvalidParameterPropertyType,
propertySyntax?.Type.GetLocation() ?? propertyLocation,
propertyType));
}

if (!hasParameter)
{
seenParametersWithTheirRequirements.Add(parameterIndex, isRequired);
parametersProperties.Add(parameterIndex, property);
}
}
}

static (ParseStrategy, bool IsNullable) GetParseStrategy(ITypeSymbol type, KnownTypes knownTypes)
var lastSeenParameterIndex = 0;
var parameterRequirements = ImmutableArray.CreateBuilder<bool>();

foreach (var pair in seenParametersWithTheirRequirements.OrderBy(static p => p.Key))
{
var index = pair.Key;

if (index > (lastSeenParameterIndex + 1))
{
if (index - lastSeenParameterIndex == 2)
{
context.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.MissingParameterWithIndex,
optionsType.Locations.First(),
(index - 1).ToString()));
}
else
{
context.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.MissingParametersWithIndexes,
optionsType.Locations.First(),
(lastSeenParameterIndex + 1).ToString(),
(index - 1).ToString()));
}
}

lastSeenParameterIndex = index;
parameterRequirements.Add(pair.Value);
}

var canNextParameterBeRequired = true;

for (var i = 0; i < parameterRequirements.Count; i++)
{
var isRequired = parameterRequirements[i];

if (isRequired)
{
if (!canNextParameterBeRequired)
{
context.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.RequiredCanOnlyBeFirstNParametersInARow,
parametersProperties[i].Locations.First()));
}
}
else
{
canNextParameterBeRequired = false;
}
}

static (ParseStrategy, bool IsNullable, bool IsSequence) GetParseStrategy(ITypeSymbol type, KnownTypes knownTypes)
{
if (type is not INamedTypeSymbol namedType)
{
return default;
}

var isNullable = false;
var isSequence = false;

if (namedType is { ConstructedFrom.SpecialType: SpecialType.System_Nullable_T, TypeArguments: [var nullableUnderlyingType] })
{
Expand Down Expand Up @@ -307,10 +431,12 @@ property.SetMethod is not null &&
{
return default;
}

isSequence = true;
}
}

return (namedType.GetPrimaryParseStrategy(), isNullable);
return (namedType.GetPrimaryParseStrategy(), isNullable, isSequence);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public sealed partial class ArgumentParserAnalyzer : DiagnosticAnalyzer
DiagnosticDescriptors.RequiredPropertyMustParticipateInArgumentParsing,
DiagnosticDescriptors.PropertyIsNotAccessible,
DiagnosticDescriptors.PropertyMustHaveAccessibleSetter,
// ARGP0011
DiagnosticDescriptors.InvalidShortName,
DiagnosticDescriptors.InvalidLongName,
DiagnosticDescriptors.DuplicateShortName,
Expand All @@ -27,7 +28,15 @@ public sealed partial class ArgumentParserAnalyzer : DiagnosticAnalyzer
// ARGP0017
// ARGP0018
DiagnosticDescriptors.RequiredBoolOption,
DiagnosticDescriptors.RequiredNullableOption);
DiagnosticDescriptors.RequiredNullableOption,
// ARGP0021
DiagnosticDescriptors.NegativeParameterIndex,
DiagnosticDescriptors.DuplicateParameterIndex,
DiagnosticDescriptors.InvalidParameterPropertyType,
DiagnosticDescriptors.MissingParameterWithIndex,
DiagnosticDescriptors.MissingParametersWithIndexes,
DiagnosticDescriptors.RequiredCanOnlyBeFirstNParametersInARow,
DiagnosticDescriptors.InvalidParameterName);

public override void Initialize(AnalysisContext context)
{
Expand Down
Loading

0 comments on commit 1b19a39

Please sign in to comment.