-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathGetHashCodeGenerator.cs
More file actions
105 lines (85 loc) · 5.24 KB
/
GetHashCodeGenerator.cs
File metadata and controls
105 lines (85 loc) · 5.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CodeGeneration;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
[Generator]
public class GetHashCodeGenerator : ISourceGenerator
{
#pragma warning disable RS2008 // Enable analyzer release tracking
private static readonly LocalizableString Title_0 = "Types decorated with " + typeof(CodeGeneration_Attributes.GenerateEqualsAttribute).FullName + " must not be partial";
private static readonly LocalizableString MessageFormat_0 = "Type {0} decorated with " + typeof(CodeGeneration_Attributes.GenerateEqualsAttribute).FullName + " is declared non-partial";
public static readonly DiagnosticDescriptor error = new DiagnosticDescriptor(id: "GHCG0",
Title_0,
MessageFormat_0,
category: "Design",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
#pragma warning restore RS2008 // Enable analyzer release tracking
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new AttributeSyntaxReceiver("GenerateGetHashCode", "CollectionValueEquality"));
}
public void Execute(GeneratorExecutionContext context)
{
AttributeSyntaxReceiver receiver = context.SyntaxReceiver as AttributeSyntaxReceiver;
if (receiver == null) return;
foreach (var typeDecl in receiver.TypeDeclarations)
{
// Get semantic model to allow for more strictly checking attribute
var model = context.Compilation.GetSemanticModel(typeDecl.SyntaxTree);
var symbol = model.GetDeclaredSymbol(typeDecl);
// Check whether attribute actually is the correct attribute
bool isDecorated = false;
foreach (var attributeData in symbol.GetAttributes())
{
var attClass = attributeData.AttributeClass;
if (attClass.ToDisplayString() == typeof(CodeGeneration_Attributes.GenerateGetHashCodeAttribute).FullName
|| attClass.ToDisplayString() == typeof(CodeGeneration_Attributes.CollectionValueEqualityAttribute).FullName)
{
isDecorated = true;
break;
}
}
if (!isDecorated) continue;
// Check whether the type is declared partial; if not, error => attribute effectively only allowed on partial type declarations
// Could theoretically move to SyntaxReceiver; wont be able to throw an error though (there might be false positives)
if (!typeDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))
{
context.ReportDiagnostic(Diagnostic.Create(error, typeDecl.GetLocation(), symbol.Name + " is not declared partial!"));
}
// Create the source
string partialSource = Utility.BuildSource(symbol, GetContents(context, symbol));
context.AddSource($"{symbol.Name}.g.cs", SourceText.From(partialSource, Encoding.UTF8));
}
}
private string GetContents(GeneratorExecutionContext context, INamedTypeSymbol symbol)
{
StringBuilder sb = new StringBuilder();
sb.Append("///<summary>\r\n///Serves as the default hash function.\r\n///IEnumerables are hashed element-wise.\r\n///</summary>\r\n///<returns>A hash code for the current object.</returns>\r\n");
sb.Append($"[System.CodeDom.Compiler.GeneratedCode(\"{Utility.ToolName}\", \"{Utility.ToolVersion}\")]\r\npublic override int GetHashCode()\r\n{{\r\n\tHashCode hash = new HashCode();");
ISymbol iEnumerableSymbol = context.Compilation.GetSpecialType(SpecialType.System_Collections_IEnumerable);
ISymbol stringSymbol = context.Compilation.GetSpecialType(SpecialType.System_String);
List<IFieldSymbol> members = symbol.GetMembers().OfType<IFieldSymbol>().ToList();
for (int i = 0; i < members.Count; i++)
{
ISymbol assoc = members[i].AssociatedSymbol ?? members[i];
if (assoc.GetAttributes().Any(attr => attr.AttributeClass.ToDisplayString() == typeof(CodeGeneration_Attributes.IgnoreEqualityAttribute).FullName))
continue;
if (!SymbolEqualityComparer.Default.Equals(members[i].Type, stringSymbol)
&& members[i].Type.AllInterfaces.Any(sym => SymbolEqualityComparer.Default.Equals(sym, iEnumerableSymbol)))
{
sb.Append($"\r\n\tif ({assoc.Name} != null)\r\n\t{{\r\n\t\tforeach (var element in {assoc.Name})\r\n\t\t{{\r\n\t\t\thash.Add(element);\r\n\t\t}}\r\n\t}}\r\n\telse\r\n\t{{\r\n\t\thash.Add({assoc.Name});\r\n\t}}");
}
else
{
sb.Append($"\r\n\thash.Add(this.{assoc.Name});");
}
}
sb.Append("\r\n\treturn hash.ToHashCode();\r\n}");
return sb.ToString();
}
}