Skip to content

Commit

Permalink
Args of switch/map must be named.
Browse files Browse the repository at this point in the history
  • Loading branch information
PawelGerr committed Sep 9, 2024
1 parent 2f90db5 commit 1d25cfa
Show file tree
Hide file tree
Showing 5 changed files with 2,060 additions and 1,246 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,44 @@ public static void Demo(ILogger logger)
logger.Information("TextOrNumber from string: Value = {Value}", textOrNumberFromString.Value);
logger.Information("TextOrNumber from int: Value = {Value}", textOrNumberFromInt.Value);

textOrNumberFromString.Switch(text: s => logger.Information("String Action: {Text}", s),
number: i => logger.Information("Int Action: {Number}", i));
textOrNumberFromString.Switch(text: s => logger.Information("[Switch] String Action: {Text}", s),
number: i => logger.Information("[Switch] Int Action: {Number}", i));

textOrNumberFromString.SwitchPartially(@default: i => logger.Information("[SwitchPartially] Default Action: {Number}", i),
text: s => logger.Information("[SwitchPartially] String Action: {Text}", s));

textOrNumberFromString.Switch(logger,
text: static (l, s) => l.Information("String Action with context: {Text}", s),
number: static (l, i) => l.Information("Int Action with context: {Number}", i));
text: static (l, s) => l.Information("[Switch] String Action with context: {Text}", s),
number: static (l, i) => l.Information("[Switch] Int Action with context: {Number}", i));

textOrNumberFromString.SwitchPartially(logger,
@default: static (l, i) => l.Information("[SwitchPartially] Default Action with context: {Number}", i),
text: static (l, s) => l.Information("[SwitchPartially] String Action with context: {Text}", s));

var switchResponse = textOrNumberFromInt.Switch(text: static s => $"String Func: {s}",
number: static i => $"Int Func: {i}");
var switchResponse = textOrNumberFromInt.Switch(text: static s => $"[Switch] String Func: {s}",
number: static i => $"[Switch] Int Func: {i}");
logger.Information("{Response}", switchResponse);

var switchPartiallyResponse = textOrNumberFromInt.SwitchPartially(@default: static i => $"[SwitchPartially] Default Func: {i}",
text: static s => $"[SwitchPartially] String Func: {s}");
logger.Information("{Response}", switchPartiallyResponse);

var switchResponseWithContext = textOrNumberFromInt.Switch(123.45,
text: static (ctx, s) => $"String Func with context: {ctx} | {s}",
number: static (ctx, i) => $"Int Func with context: {ctx} | {i}");
text: static (ctx, s) => $"[Switch] String Func with context: {ctx} | {s}",
number: static (ctx, i) => $"[Switch] Int Func with context: {ctx} | {i}");
logger.Information("{Response}", switchResponseWithContext);

var mapResponse = textOrNumberFromString.Map(text: "Mapped string",
number: "Mapped int");
var switchPartiallyResponseWithContext = textOrNumberFromInt.SwitchPartially(123.45,
text: static (ctx, s) => $"[SwitchPartially] String Func with context: {ctx} | {s}",
@default: static (ctx, i) => $"[SwitchPartially] Default Func with context: {ctx} | {i}");
logger.Information("{Response}", switchPartiallyResponseWithContext);

var mapResponse = textOrNumberFromString.Map(text: "[Map] Mapped string",
number: "[Map] Mapped int");
logger.Information("{Response}", mapResponse);

var mapPartiallyResponse = textOrNumberFromString.MapPartially(@default: "[MapPartially] Mapped default",
text: "[MapPartially] Mapped string");
logger.Information("{Response}", mapPartiallyResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,32 +72,64 @@ private static void AnalyzeMethodCall(OperationAnalysisContext context)
return;
}

if (!operation.Instance.Type.IsEnum(out var attribute))
return;

var isValidatable = attribute.FindIsValidatable() ?? false;
var nonIgnoredMembers = operation.Instance.Type.GetNonIgnoredMembers();
var items = operation.Instance.Type.GetEnumItems(nonIgnoredMembers);
var args = operation.Arguments;
if (operation.Instance.Type.IsEnum(out var attribute))
{
var isValidatable = attribute.FindIsValidatable() ?? false;
var nonIgnoredMembers = operation.Instance.Type.GetNonIgnoredMembers();
var items = operation.Instance.Type.GetEnumItems(nonIgnoredMembers);

AnalyzeIndexBasedSwitchMap(context, items, args, operation, isValidatable);
AnalyzeEnumSwitchMap(context,
items,
operation.Arguments,
operation,
isValidatable);
}
else if (operation.Instance.Type.IsUnionAttribute(out attribute)
&& attribute.AttributeClass is not null)
{
AnalyzeUnionSwitchMap(context,
attribute.AttributeClass.TypeArguments,
operation.Arguments,
operation);
}
}

private static void AnalyzeIndexBasedSwitchMap(
private static void AnalyzeEnumSwitchMap(
OperationAnalysisContext context,
ImmutableArray<IFieldSymbol> items,
ImmutableArray<IArgumentOperation> args,
IInvocationOperation operation,
bool isValidatable)
{
// The enum must have a type
var numberOfCallbacks = items.Length
+ (isValidatable ? 1 : 0)
+ (operation.TargetMethod.Name is _SWITCH_PARTIALLY or _MAP_PARTIALLY ? 1 : 0);

AnalyzeSwitchMap(context, args, operation, numberOfCallbacks);
}

private static void AnalyzeUnionSwitchMap(
OperationAnalysisContext context,
ImmutableArray<ITypeSymbol> memberTypes,
ImmutableArray<IArgumentOperation> args,
IInvocationOperation operation)
{
var numberOfCallbacks = memberTypes.Length
+ (operation.TargetMethod.Name is _SWITCH_PARTIALLY or _MAP_PARTIALLY ? 1 : 0);

AnalyzeSwitchMap(context, args, operation, numberOfCallbacks);
}

private static void AnalyzeSwitchMap(
OperationAnalysisContext context,
ImmutableArray<IArgumentOperation> args,
IInvocationOperation operation,
int numberOfCallbacks)
{
if (operation.Instance?.Type is null || args.IsDefaultOrEmpty)
return;

var hasNonNamedParameters = false;
var numberOfCallbacks = items.Length
+ (isValidatable ? 1 : 0)
+ (operation.TargetMethod.Name is _SWITCH_PARTIALLY or _MAP_PARTIALLY ? 1 : 0);
var argsStartIndex = operation.TargetMethod.Parameters.Length == numberOfCallbacks ? 0 : 1;

for (var argIndex = argsStartIndex; argIndex < args.Length; argIndex++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal static class DiagnosticsDescriptors
public static readonly DiagnosticDescriptor PrimaryConstructorNotAllowed = new("TTRESG043", "Primary constructor is not allowed in Smart Enums and Value Objects", "Primary constructor is not allowed in Smart Enum/Value Object of type '{0}'", nameof(ThinktectureRuntimeExtensionsAnalyzer), DiagnosticSeverity.Error, true);
public static readonly DiagnosticDescriptor CustomKeyMemberImplementationNotFound = new("TTRESG044", "Custom implementation of the key member not found", $"Provide a custom implementation of the key member. Implement a field or property '{{0}}'. Use '{Constants.Attributes.Properties.KEY_MEMBER_NAME}' to change the name.", nameof(ThinktectureRuntimeExtensionsAnalyzer), DiagnosticSeverity.Error, true);
public static readonly DiagnosticDescriptor CustomKeyMemberImplementationTypeMismatch = new("TTRESG045", "Key member type mismatch", "The type of the key member '{0}' must be '{2}' instead of '{1}'", nameof(ThinktectureRuntimeExtensionsAnalyzer), DiagnosticSeverity.Error, true);
public static readonly DiagnosticDescriptor IndexBasedSwitchAndMapMustUseNamedParameters = new("TTRESG046", "The arguments of \"Switch\" and \"Map\" must named", "Not all arguments of \"Switch/Map\" on the enumeration '{0}' are named", nameof(ThinktectureRuntimeExtensionsAnalyzer), DiagnosticSeverity.Error, true);
public static readonly DiagnosticDescriptor IndexBasedSwitchAndMapMustUseNamedParameters = new("TTRESG046", "The arguments of \"Switch\" and \"Map\" must named", "Not all arguments of \"Switch/Map\" on type '{0}' are named", nameof(ThinktectureRuntimeExtensionsAnalyzer), DiagnosticSeverity.Error, true);
public static readonly DiagnosticDescriptor TypeMustBeClass = new("TTRESG047", "The type must be a class", "The type '{0}' must be a class", nameof(ThinktectureRuntimeExtensionsAnalyzer), DiagnosticSeverity.Error, true);

public static readonly DiagnosticDescriptor ErrorDuringCodeAnalysis = new("TTRESG098", "Error during code analysis", "Error during code analysis of '{0}': '{1}'", nameof(ThinktectureRuntimeExtensionsAnalyzer), DiagnosticSeverity.Warning, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ public static bool IsUnionAttribute(this ITypeSymbol? attributeType)
return attributeType is { Name: Constants.Attributes.Union.NAME, ContainingNamespace: { Name: Constants.Attributes.Union.NAMESPACE, ContainingNamespace.IsGlobalNamespace: true } };
}

public static bool IsUnionAttribute(
[NotNullWhen(true)] this ITypeSymbol? unionType,
[NotNullWhen(true)] out AttributeData? unionAttribute)
{
unionAttribute = unionType?.FindAttribute(static attributeClass => attributeClass.IsUnionAttribute());

return unionAttribute is not null;
}

public static bool IsValueObjectMemberEqualityComparerAttribute(this ITypeSymbol? attributeType)
{
if (attributeType is null || attributeType.TypeKind == TypeKind.Error)
Expand Down
Loading

0 comments on commit 1d25cfa

Please sign in to comment.