Skip to content

Commit

Permalink
add diagnostics
Browse files Browse the repository at this point in the history
  • Loading branch information
pwelter34 committed Sep 5, 2024
1 parent 257a9ab commit d48db3e
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ updates:
time: "02:00"
timezone: "America/Chicago"
open-pull-requests-limit: 10
ignore:
- dependency-name: "Microsoft.CodeAnalysis.CSharp"
groups:
Azure:
patterns:
Expand Down
1 change: 1 addition & 0 deletions Equatable.Generator.sln
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Equatable.SourceGenerator",
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{9ABD9B14-9E53-463A-96B0-FA7F503BA826}"
ProjectSection(SolutionItems) = preProject
.github\dependabot.yml = .github\dependabot.yml
src\Directory.Build.props = src\Directory.Build.props
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
README.md = README.md
Expand Down
43 changes: 43 additions & 0 deletions src/Equatable.SourceGenerator/DiagnosticDescriptors.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Microsoft.CodeAnalysis;

namespace Equatable.SourceGenerator;

internal static class DiagnosticDescriptors
{
public static DiagnosticDescriptor InvalidStringEqualityAttributeUsage => new(
id: "EQ0010",
title: "Invalid String Equality Attribute Usage",
messageFormat: "Invalid String equality attribute usage for property {0}. Property return type is not a string",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true
);

public static DiagnosticDescriptor InvalidDictionaryEqualityAttributeUsage => new(
id: "EQ0011",
title: "Invalid Dictionary Equality Attribute Usage",
messageFormat: "Invalid Dictionary equality attribute usage for property {0}. Property return type does not implement IDictionary<TKey, TValue>",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true
);

public static DiagnosticDescriptor InvalidHashSetEqualityAttributeUsage => new(
id: "EQ0012",
title: "Invalid HashSet Equality Attribute Usage",
messageFormat: "Invalid HashSet equality attribute usage for property {0}. Property return type does not implement IEnumerable<T>",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true
);

public static DiagnosticDescriptor InvalidSequenceEqualityAttributeUsage => new(
id: "EQ0013",
title: "Invalid Sequence Equality Attribute Usage",
messageFormat: "Invalid Sequence equality attribute usage for property {0}. Property return type does not implement IEnumerable<T>",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true
);

}
132 changes: 129 additions & 3 deletions src/Equatable.SourceGenerator/EquatableGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken
if (context.TargetSymbol is not INamedTypeSymbol targetSymbol)
return null;

var diagnostics = new List<Diagnostic>();

var fullyQualified = targetSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var classNamespace = targetSymbol.ContainingNamespace.ToDisplayString();
var className = targetSymbol.Name;
Expand All @@ -86,7 +88,7 @@ private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken
var propertySymbols = GetProperties(targetSymbol, baseHashCode == null && baseEquatable == null);

var propertyArray = propertySymbols
.Select(CreateProperty)
.Select(symbol => CreateProperty(diagnostics, symbol))
.ToArray() ?? [];

// the seed value of the hash code method
Expand Down Expand Up @@ -116,7 +118,7 @@ private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken
SeedHash: seedHash
);

return new EquatableContext(entity, null);
return new EquatableContext(entity, diagnostics.ToArray());
}


Expand Down Expand Up @@ -148,7 +150,7 @@ private static IEnumerable<IPropertySymbol> GetProperties(INamedTypeSymbol targe
return properties.Values;
}

private static EquatableProperty CreateProperty(IPropertySymbol propertySymbol)
private static EquatableProperty CreateProperty(List<Diagnostic> diagnostics, IPropertySymbol propertySymbol)
{
var format = SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
var propertyType = propertySymbol.Type.ToDisplayString(format);
Expand All @@ -172,6 +174,17 @@ private static EquatableProperty CreateProperty(IPropertySymbol propertySymbol)
if (!comparerType.HasValue)
continue;

var diagnostic = ValidateComparer(propertySymbol, comparerType);
if (diagnostic != null)
{
diagnostics.Add(diagnostic);

return new EquatableProperty(
propertyName,
propertyType,
ComparerTypes.Default);
}

return new EquatableProperty(
propertyName,
propertyType,
Expand All @@ -186,6 +199,63 @@ private static EquatableProperty CreateProperty(IPropertySymbol propertySymbol)
ComparerTypes.Default);
}

private static Diagnostic? ValidateComparer(IPropertySymbol propertySymbol, ComparerTypes? comparerType)
{
// don't need to validate these types
if (comparerType is null or ComparerTypes.Default or ComparerTypes.Reference or ComparerTypes.Custom)
return null;

if (comparerType == ComparerTypes.String)
{
if (IsString(propertySymbol.Type))
return null;

return Diagnostic.Create(
DiagnosticDescriptors.InvalidStringEqualityAttributeUsage,
propertySymbol.Locations.FirstOrDefault(),
propertySymbol.Name
);
}

if (comparerType == ComparerTypes.Dictionary)
{
if (propertySymbol.Type.AllInterfaces.Any(IsDictionary))
return null;

return Diagnostic.Create(
DiagnosticDescriptors.InvalidDictionaryEqualityAttributeUsage,
propertySymbol.Locations.FirstOrDefault(),
propertySymbol.Name
);
}

if (comparerType == ComparerTypes.HashSet)
{
if (propertySymbol.Type.AllInterfaces.Any(IsEnumerable))
return null;

return Diagnostic.Create(
DiagnosticDescriptors.InvalidHashSetEqualityAttributeUsage,
propertySymbol.Locations.FirstOrDefault(),
propertySymbol.Name
);
}

if (comparerType == ComparerTypes.Sequence)
{
if (propertySymbol.Type.AllInterfaces.Any(IsEnumerable))
return null;

return Diagnostic.Create(
DiagnosticDescriptors.InvalidSequenceEqualityAttributeUsage,
propertySymbol.Locations.FirstOrDefault(),
propertySymbol.Name
);
}

return null;
}


private static (ComparerTypes? comparerType, string? comparerName, string? comparerInstance) GetComparer(AttributeData? attribute)
{
Expand Down Expand Up @@ -293,6 +363,62 @@ private static bool IsValueType(INamedTypeSymbol targetSymbol)
};
}

private static bool IsEnumerable(INamedTypeSymbol targetSymbol)
{
return targetSymbol is
{
Name: "IEnumerable",
IsGenericType: true,
TypeArguments.Length: 1,
TypeParameters.Length: 1,
ContainingNamespace:
{
Name: "Generic",
ContainingNamespace:
{
Name: "Collections",
ContainingNamespace:
{
Name: "System"
}
}
}
};
}

private static bool IsDictionary(INamedTypeSymbol targetSymbol)
{
return targetSymbol is
{
Name: "IDictionary",
IsGenericType: true,
TypeArguments.Length: 2,
TypeParameters.Length: 2,
ContainingNamespace:
{
Name: "Generic",
ContainingNamespace:
{
Name: "Collections",
ContainingNamespace:
{
Name: "System"
}
}
}
};
}

private static bool IsString(ITypeSymbol targetSymbol)
{
return targetSymbol is
{
Name: nameof(String),
ContainingNamespace.Name: "System"
};
}


private static EquatableArray<ContainingClass> GetContainingTypes(INamedTypeSymbol targetSymbol)
{
if (targetSymbol.ContainingType is null)
Expand Down
Loading

0 comments on commit d48db3e

Please sign in to comment.