Skip to content

Commit 9014888

Browse files
committed
Add project files.
1 parent c039f1d commit 9014888

21 files changed

+1021
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
; Shipped analyzer releases
2+
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
3+
4+
## Release 1.0
5+
6+
### New Rules
7+
8+
Rule ID | Category | Severity | Notes
9+
--------|----------|----------|-------
10+
NC001 | Usage | Warning | NamedComparerAnalyzer
11+
TND001 | Usage | Warning | ToDictionaryAnalyzer
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
; Unshipped analyzer release
2+
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
3+
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp.Syntax;
3+
using Microsoft.CodeAnalysis.Text;
4+
using System.Collections.Generic;
5+
using System.Collections.Immutable;
6+
using System.Linq;
7+
using System.Text;
8+
9+
namespace EnumSourceGenerator;
10+
11+
[Generator]
12+
public class EnumGenerator : IIncrementalGenerator
13+
{
14+
public void Initialize(IncrementalGeneratorInitializationContext context)
15+
{
16+
var classDeclarations = context.SyntaxProvider
17+
.CreateSyntaxProvider(
18+
predicate: static (node, _) => node is ClassDeclarationSyntax { BaseList: not null },
19+
transform: static (context, _) => GetClassSymbol(context))
20+
.Where(static symbol => symbol is not null)
21+
.Select(static (symbol, _) => symbol!)
22+
.Collect();
23+
24+
context.RegisterSourceOutput(classDeclarations, (ctx, classes) =>
25+
{
26+
foreach (var classSymbol in classes)
27+
{
28+
// Find the IContent<T> implementation
29+
var iContentInterface = classSymbol.AllInterfaces.FirstOrDefault(i => i.Name == "IContent");
30+
if (iContentInterface?.TypeArguments.FirstOrDefault() is not INamedTypeSymbol typeArgument) continue;
31+
32+
// Extract enum member names from the All property of the class
33+
var enumMembers = ExtractEnumMembers(ctx, classSymbol).ToImmutableArray();
34+
35+
var source = GenerateEnumSource(classSymbol.Name, enumMembers);
36+
ctx.AddSource($"{classSymbol.Name}TypeEnum.g.cs", SourceText.From(source, Encoding.UTF8));
37+
38+
var helper = GeneratePartialHelper(classSymbol.Name,
39+
classSymbol.ContainingNamespace.ToDisplayString(),
40+
typeArgument.ToDisplayString(), enumMembers);
41+
ctx.AddSource($"{classSymbol.Name}Helper.g.cs", SourceText.From(helper, Encoding.UTF8));
42+
}
43+
});
44+
}
45+
46+
private static INamedTypeSymbol? GetClassSymbol(GeneratorSyntaxContext context)
47+
=> context.Node is ClassDeclarationSyntax classDecl
48+
? context.SemanticModel.GetDeclaredSymbol(classDecl) as INamedTypeSymbol
49+
: null;
50+
51+
private static IEnumerable<string> ExtractEnumMembers(
52+
SourceProductionContext ctx,
53+
INamedTypeSymbol classSymbol)
54+
{
55+
var allProperty = classSymbol
56+
.GetMembers()
57+
.OfType<IPropertySymbol>()
58+
.FirstOrDefault(p => p.Name == "All");
59+
if (allProperty == null)
60+
{
61+
ctx.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("GEN001",
62+
"All Property Not Found", "The All property in class {0} was not found.",
63+
"Generator", DiagnosticSeverity.Warning, true), Location.None));
64+
yield break;
65+
}
66+
67+
// Get the syntax reference for the property and ensure it's not null
68+
if (allProperty.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax()
69+
is not PropertyDeclarationSyntax allPropertySyntax || allPropertySyntax.Initializer == null)
70+
{
71+
ctx.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("GEN002",
72+
"No Initializer Found", "No initializer was found for the All property in class {0}.",
73+
"Generator", DiagnosticSeverity.Warning, true), Location.None));
74+
yield break;
75+
}
76+
77+
// Get the initializer expression value
78+
var initializer = allPropertySyntax.Initializer.Value;
79+
80+
// Handle explicit array creation (e.g., `new Material[] { ... }`)
81+
if (initializer is ArrayCreationExpressionSyntax arrayCreation)
82+
{
83+
foreach (var enumName in ExtractFromArrayInitializer(ctx, arrayCreation.Initializer))
84+
{
85+
yield return enumName;
86+
}
87+
}
88+
// Handle implicit array creation with `{}` or `[]` (e.g., `Material[] All = [ ... ];`)
89+
else if (initializer is ImplicitArrayCreationExpressionSyntax implicitArrayCreation)
90+
{
91+
foreach (var enumName in ExtractFromArrayInitializer(ctx, implicitArrayCreation.Initializer))
92+
{
93+
yield return enumName;
94+
}
95+
}
96+
// Handle collection expressions `[]` directly
97+
else if (initializer is InitializerExpressionSyntax initializerExpression)
98+
{
99+
foreach (var enumName in ExtractFromArrayInitializer(ctx, initializerExpression))
100+
{
101+
yield return enumName;
102+
}
103+
}
104+
// Handle collection expressions `[]` directly
105+
else if (initializer is CollectionExpressionSyntax collection)
106+
{
107+
foreach (var enumName in ExtractFromCollection(ctx, collection))
108+
{
109+
yield return enumName;
110+
}
111+
}
112+
else
113+
{
114+
ctx.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("GEN004",
115+
"Incorrect Initializer",
116+
"The initializer is not a recognized array creation syntax." +
117+
$"Of type {allPropertySyntax.Initializer.Value.GetType()}",
118+
"Generator", DiagnosticSeverity.Warning, true), Location.None));
119+
}
120+
}
121+
122+
private static IEnumerable<string> ExtractFromCollection(
123+
SourceProductionContext ctx,
124+
CollectionExpressionSyntax collection)
125+
{
126+
if (collection.Elements.Count == 0)
127+
{
128+
ctx.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("GEN008",
129+
"Empty Collection", "The array collection is empty.", "Generator",
130+
DiagnosticSeverity.Warning, true), Location.None));
131+
yield break;
132+
}
133+
134+
foreach (var element in collection.Elements)
135+
{
136+
if (element is ExpressionElementSyntax objectExpression )
137+
{
138+
// Handle object initializers like `new("...")`
139+
if (objectExpression.Expression is ObjectCreationExpressionSyntax objectCreation)
140+
{
141+
var firstArgument = objectCreation.ArgumentList?.Arguments.FirstOrDefault();
142+
if (firstArgument?.Expression is LiteralExpressionSyntax literal)
143+
yield return SanitizeEnumName(literal.Token.ValueText);
144+
}
145+
// Handle shorthand object initializers like `new("...")`
146+
else if (objectExpression.Expression is ImplicitObjectCreationExpressionSyntax implicitCreation)
147+
{
148+
var firstArgument = implicitCreation.ArgumentList?.Arguments.FirstOrDefault();
149+
if (firstArgument?.Expression is LiteralExpressionSyntax literal)
150+
yield return SanitizeEnumName(literal.Token.ValueText);
151+
}
152+
}
153+
}
154+
}
155+
156+
private static IEnumerable<string> ExtractFromArrayInitializer(
157+
SourceProductionContext ctx,
158+
InitializerExpressionSyntax? initializer)
159+
{
160+
if (initializer == null)
161+
{
162+
ctx.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("GEN005",
163+
"Empty Initializer", "The array initializer is empty.", "Generator",
164+
DiagnosticSeverity.Warning, true), Location.None));
165+
yield break;
166+
}
167+
168+
foreach (var expression in initializer.Expressions)
169+
{
170+
// Handle object initializers like `new("...")`
171+
if (expression is ObjectCreationExpressionSyntax objectCreation)
172+
{
173+
var firstArgument = objectCreation.ArgumentList?.Arguments.FirstOrDefault();
174+
if (firstArgument?.Expression is LiteralExpressionSyntax literal)
175+
yield return SanitizeEnumName(literal.Token.ValueText);
176+
}
177+
// Handle shorthand object initializers like `new("...")`
178+
else if (expression is ImplicitObjectCreationExpressionSyntax implicitCreation)
179+
{
180+
var firstArgument = implicitCreation.ArgumentList?.Arguments.FirstOrDefault();
181+
if (firstArgument?.Expression is LiteralExpressionSyntax literal)
182+
yield return SanitizeEnumName(literal.Token.ValueText);
183+
}
184+
}
185+
}
186+
187+
private static string GenerateEnumSource(string className, ImmutableArray<string> enumMembers)
188+
{
189+
var membersSource = string.Join(",\n", enumMembers.Select(m => $" {m}"));
190+
return $@"// Auto-generated code
191+
namespace ContentEnums
192+
{{
193+
public enum {className}Type
194+
{{
195+
{membersSource}
196+
}}
197+
}}";
198+
}
199+
200+
private static string GeneratePartialHelper(string className, string fullNamespace, string typeArgument, ImmutableArray<string> enumMembers)
201+
{
202+
var membersSource = string.Join(";\n", enumMembers.Select((m, i) => $" public {typeArgument} {m} => All[{i}]"));
203+
return $@"// Auto-generated code
204+
using ContentEnums;
205+
206+
namespace {fullNamespace}
207+
{{
208+
public partial class {className}
209+
{{
210+
public {typeArgument} Get({className}Type type) => All[(int)type];
211+
public {typeArgument} this[{className}Type type] => All[(int)type];
212+
public {typeArgument} GetById(int id) => All[id];
213+
{membersSource};
214+
}}
215+
}}";
216+
}
217+
218+
private static string SanitizeEnumName(string name)
219+
{
220+
// Remove invalid characters and replace spaces with underscores
221+
StringBuilder builder = new();
222+
foreach (var ch in name)
223+
{
224+
if (char.IsLetterOrDigit(ch) || ch == '_')
225+
builder.Append(ch);
226+
}
227+
228+
return builder.ToString();
229+
}
230+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Library</OutputType>
5+
<TargetFramework>netstandard2.0</TargetFramework>
6+
<LangVersion>latest</LangVersion>
7+
<Nullable>enable</Nullable>
8+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
9+
<IsRoslynComponent>true</IsRoslynComponent>
10+
<IncludeBuildOutput>false</IncludeBuildOutput>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0">
15+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
16+
<PrivateAssets>all</PrivateAssets>
17+
</PackageReference>
18+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<AdditionalFiles Include="AnalyzerReleases.Shipped.md" />
23+
<AdditionalFiles Include="AnalyzerReleases.Unshipped.md" />
24+
</ItemGroup>
25+
26+
</Project>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp;
3+
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
using Microsoft.CodeAnalysis.Diagnostics;
5+
using System.Collections.Immutable;
6+
7+
namespace EnumSourceGenerator;
8+
9+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
10+
public class NamedComparerAnalyzer : DiagnosticAnalyzer
11+
{
12+
public const string DiagnosticId = "NC001";
13+
14+
private static readonly DiagnosticDescriptor _rule = new(
15+
DiagnosticId,
16+
"Dictionary<TKey, TValue> must use NamedComparer<T> for INamed keys",
17+
"Dictionary<{0}, {1}> should specify NamedComparer<{0}> as a comparer",
18+
"Usage",
19+
DiagnosticSeverity.Warning,
20+
isEnabledByDefault: true);
21+
22+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [_rule];
23+
24+
public override void Initialize(AnalysisContext context)
25+
{
26+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
27+
context.EnableConcurrentExecution();
28+
context.RegisterSyntaxNodeAction(AnalyzeObjectCreation, SyntaxKind.ObjectCreationExpression);
29+
}
30+
31+
private static void AnalyzeObjectCreation(SyntaxNodeAnalysisContext context)
32+
{
33+
if (context.Node is ObjectCreationExpressionSyntax objectCreation &&
34+
objectCreation.Type is GenericNameSyntax genericType &&
35+
genericType.Identifier.Text == "Dictionary" &&
36+
genericType.TypeArgumentList.Arguments.Count == 2)
37+
{
38+
// Extract key type
39+
var keyType = context.SemanticModel.GetTypeInfo(genericType.TypeArgumentList.Arguments[0]).Type;
40+
if (keyType == null)
41+
return;
42+
43+
// Check if key type implements INamed
44+
var namedInterface = context.Compilation.GetTypeByMetadataName("ConsoleHero.Generator.INamed");
45+
if (namedInterface == null || !keyType.AllInterfaces.Contains(namedInterface))
46+
return;
47+
48+
// Check if a comparer is provided (should be 2nd or 3rd constructor argument)
49+
var argumentList = objectCreation.ArgumentList?.Arguments;
50+
if (!argumentList.HasValue || argumentList.Value.Count < 3)
51+
{
52+
var diagnostic = Diagnostic.Create(_rule, objectCreation.GetLocation(),
53+
keyType.Name, genericType.TypeArgumentList.Arguments[1].ToString());
54+
context.ReportDiagnostic(diagnostic);
55+
}
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)