Skip to content

Commit

Permalink
Merge pull request #2 from wieslawsoltes/FixMultiplePartialClasses
Browse files Browse the repository at this point in the history
Fix multiple partial classes
  • Loading branch information
wieslawsoltes authored Nov 27, 2024
2 parents 944ab0d + 76fded0 commit 9fb3aef
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 36 deletions.
77 changes: 55 additions & 22 deletions ReactiveGenerator/ReactiveGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
Expand All @@ -17,11 +18,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
// Register the attribute source
context.RegisterPostInitializationOutput(ctx =>
{
// Add the attribute source
ctx.AddSource("ReactiveAttribute.g.cs", SourceText.From(AttributeSource, Encoding.UTF8));
});


// Get MSBuild property for enabling legacy mode
var useLegacyMode = context.AnalyzerConfigOptionsProvider
.Select((provider, _) => bool.TryParse(
Expand All @@ -34,7 +33,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var propertyDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (s, _) => IsCandidateProperty(s),
transform: (ctx, _) => GetSemanticTarget(ctx))
transform: (ctx, _) => GetPropertyInfo(ctx))
.Where(m => m is not null);

// Combine compilation, properties, and configuration
Expand All @@ -46,14 +45,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
compilationAndProperties,
(spc, source) => Execute(
source.Left.Left,
source.Left.Right.Cast<IPropertySymbol>().ToList(),
source.Left.Right.Cast<(IPropertySymbol Property, Location Location)>().ToList(),
source.Right,
spc));
}

private static bool IsCandidateProperty(SyntaxNode node)
{
// Same as before
if (node is not PropertyDeclarationSyntax propertyDeclaration)
return false;

Expand All @@ -62,10 +60,9 @@ private static bool IsCandidateProperty(SyntaxNode node)

return propertyDeclaration.AttributeLists.Count > 0;
}

private static IPropertySymbol? GetSemanticTarget(GeneratorSyntaxContext context)
private static (IPropertySymbol Property, Location Location)? GetPropertyInfo(GeneratorSyntaxContext context)
{
// Same as before
if (context.Node is not PropertyDeclarationSyntax propertyDeclaration)
return null;

Expand All @@ -79,7 +76,10 @@ private static bool IsCandidateProperty(SyntaxNode node)
if (name is "Reactive" or "ReactiveAttribute")
{
var symbol = semanticModel.GetDeclaredSymbol(propertyDeclaration);
return symbol;
if (symbol != null)
{
return (symbol, propertyDeclaration.GetLocation());
}
}
}
}
Expand Down Expand Up @@ -145,49 +145,82 @@ attr.AttributeClass is not null &&
return typeSymbol;
}

private static void Execute(
private static void Execute(
Compilation compilation,
List<IPropertySymbol> properties,
List<(IPropertySymbol Property, Location Location)> properties,
bool useLegacyMode,
SourceProductionContext context)
{
if (properties.Count == 0)
return;

// Group properties by containing type and file path
var propertyGroups = properties
.GroupBy(p => p.ContainingType, SymbolEqualityComparer.Default)
.GroupBy(
p => (Type: p.Property.ContainingType,
FilePath: p.Location.SourceTree?.FilePath ?? string.Empty),
(key, group) => new
{
TypeSymbol = key.Type,
FilePath = key.FilePath,
Properties = group.Select(g => g.Property).ToList()
},
new TypeAndPathComparer())
.ToList();

var processedTypes = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);

// Process INPC implementation for base types first
foreach (var group in propertyGroups)
{
if (group.Key is not INamedTypeSymbol typeSymbol) continue;
if (group.TypeSymbol is not INamedTypeSymbol typeSymbol) continue;

var baseType = FindFirstTypeNeedingINPC(compilation, typeSymbol);
if (baseType is not null && !processedTypes.Contains(baseType))
{
var source = GenerateClassSource(baseType, properties, implementInpc: true, useLegacyMode);
var source = GenerateClassSource(baseType, properties.Select(p => p.Property).ToList(), implementInpc: true, useLegacyMode);
if (!string.IsNullOrEmpty(source))
{
context.AddSource(
$"{baseType.Name}_ReactiveProperties.g.cs",
SourceText.From(source, Encoding.UTF8));
var sourceFilePath = Path.GetFileNameWithoutExtension(group.FilePath);
var fileName = $"{baseType.Name}.{sourceFilePath}.ReactiveProperties.g.cs";
context.AddSource(fileName, SourceText.From(source, Encoding.UTF8));
processedTypes.Add(baseType);
}
}
}

// Process each group of properties
foreach (var group in propertyGroups)
{
if (group.Key is not INamedTypeSymbol typeSymbol || processedTypes.Contains(typeSymbol)) continue;
if (group.TypeSymbol is not INamedTypeSymbol typeSymbol || processedTypes.Contains(typeSymbol))
continue;

var source = GenerateClassSource(typeSymbol, properties, implementInpc: false, useLegacyMode);
var source = GenerateClassSource(typeSymbol, group.Properties, implementInpc: false, useLegacyMode);
if (!string.IsNullOrEmpty(source))
{
context.AddSource(
$"{typeSymbol.Name}_ReactiveProperties.g.cs",
SourceText.From(source, Encoding.UTF8));
var sourceFilePath = Path.GetFileNameWithoutExtension(group.FilePath);
var fileName = $"{typeSymbol.Name}.{sourceFilePath}.ReactiveProperties.g.cs";
context.AddSource(fileName, SourceText.From(source, Encoding.UTF8));
}
}
}

private class TypeAndPathComparer : IEqualityComparer<(INamedTypeSymbol Type, string FilePath)>
{
public bool Equals((INamedTypeSymbol Type, string FilePath) x, (INamedTypeSymbol Type, string FilePath) y)
{
return SymbolEqualityComparer.Default.Equals(x.Type, y.Type) &&
string.Equals(x.FilePath, y.FilePath, StringComparison.OrdinalIgnoreCase);
}

public int GetHashCode((INamedTypeSymbol Type, string FilePath) obj)
{
unchecked
{
int hash = 17;
hash = hash * 31 + SymbolEqualityComparer.Default.GetHashCode(obj.Type);
hash = hash * 31 + StringComparer.OrdinalIgnoreCase.GetHashCode(obj.FilePath);
return hash;
}
}
}
Expand Down
56 changes: 43 additions & 13 deletions ReactiveGenerator/WhenAnyValueGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
Expand All @@ -18,7 +19,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var classDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (s, _) => IsCandidateClass(s),
transform: (ctx, _) => GetSemanticTargetForClass(ctx))
transform: (ctx, _) => GetClassInfo(ctx))
.Where(c => c is not null);

// Register the attribute source
Expand All @@ -40,7 +41,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
// Generate source
context.RegisterSourceOutput(
compilationAndClasses,
(spc, source) => Execute(source.Left, source.Right.Cast<INamedTypeSymbol>().ToList(), spc));
(spc, source) => Execute(source.Left, source.Right.Cast<(INamedTypeSymbol Symbol, Location Location)>().ToList(), spc));
}

private static bool IsCandidateClass(SyntaxNode node)
Expand All @@ -57,33 +58,62 @@ private static bool IsCandidateClass(SyntaxNode node)
return classDeclaration.Members
.OfType<PropertyDeclarationSyntax>()
.Any(p => p.AttributeLists.Count > 0 &&
p.AttributeLists.Any(al =>
al.Attributes.Any(a =>
a.Name.ToString() is "Reactive" or "ReactiveAttribute")));
p.AttributeLists.Any(al =>
al.Attributes.Any(a =>
a.Name.ToString() is "Reactive" or "ReactiveAttribute")));
}

private static INamedTypeSymbol? GetSemanticTargetForClass(GeneratorSyntaxContext context)
private static (INamedTypeSymbol Symbol, Location Location)? GetClassInfo(GeneratorSyntaxContext context)
{
var classDeclaration = (ClassDeclarationSyntax)context.Node;
var symbol = context.SemanticModel.GetDeclaredSymbol(classDeclaration);
return symbol;
return symbol != null ? (symbol, classDeclaration.GetLocation()) : null;
}

private static void Execute(
Compilation compilation,
List<INamedTypeSymbol> classes,
List<(INamedTypeSymbol Symbol, Location Location)> classes,
SourceProductionContext context)
{
if (classes.Count == 0) return;

// Generate a separate file for each class
foreach (var classSymbol in classes)
// Group classes by type and source file
var classGroups = classes
.GroupBy(
c => (Type: c.Symbol, FilePath: c.Location.SourceTree?.FilePath ?? string.Empty),
(key, group) => new { TypeSymbol = key.Type, FilePath = key.FilePath },
new TypeAndPathComparer())
.ToList();

// Generate a separate file for each class group
foreach (var group in classGroups)
{
var source = GenerateExtensionsForClass(classSymbol);
var fileName = $"{classSymbol.Name}.WhenAnyValue.g.cs";
var source = GenerateExtensionsForClass(group.TypeSymbol);
var sourceFilePath = Path.GetFileNameWithoutExtension(group.FilePath);
var fileName = $"{group.TypeSymbol.Name}.{sourceFilePath}.WhenAnyValue.g.cs";
context.AddSource(fileName, SourceText.From(source, Encoding.UTF8));
}
}

private class TypeAndPathComparer : IEqualityComparer<(INamedTypeSymbol Type, string FilePath)>
{
public bool Equals((INamedTypeSymbol Type, string FilePath) x, (INamedTypeSymbol Type, string FilePath) y)
{
return SymbolEqualityComparer.Default.Equals(x.Type, y.Type) &&
string.Equals(x.FilePath, y.FilePath, StringComparison.OrdinalIgnoreCase);
}

public int GetHashCode((INamedTypeSymbol Type, string FilePath) obj)
{
unchecked
{
int hash = 17;
hash = hash * 31 + SymbolEqualityComparer.Default.GetHashCode(obj.Type);
hash = hash * 31 + StringComparer.OrdinalIgnoreCase.GetHashCode(obj.FilePath);
return hash;
}
}
}

private static string GenerateExtensionsForClass(INamedTypeSymbol classSymbol)
{
Expand Down
5 changes: 4 additions & 1 deletion ReactiveGeneratorDemo/ViewModels/Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ public partial class Test
{
[Reactive]
public partial Person Person { get; set; }

}

public partial class Test
{
[Reactive]
public partial Car Car { get; set; }
}

0 comments on commit 9fb3aef

Please sign in to comment.