Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features/incremental source generator #4039

Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
78e3ac9
Refactor and update serialization approach
luizfbicalho Jun 15, 2024
41aeac7
Refine attribute discovery and optimize builder
luizfbicalho Jun 16, 2024
3d51c93
Merge branch 'main' into features/incremental-source-generator
luizfbicalho Jun 16, 2024
016e284
Enhanced nullability and serialization handling
luizfbicalho Jun 16, 2024
5cced59
Enhance nullable support in SerializationBuilder
luizfbicalho Jun 16, 2024
183acf3
Refactor and document type handling
luizfbicalho Jun 16, 2024
a0f372b
Enhance nullable annotations handling
luizfbicalho Jun 16, 2024
39063ae
Merge branch 'main' into features/incremental-source-generator
luizfbicalho Jun 18, 2024
0af8f1f
Merge branch 'MarimerLLC:main' into features/incremental-source-gener…
luizfbicalho Jun 19, 2024
8156d6f
Merge branch 'main' into features/incremental-source-generator
luizfbicalho Jun 20, 2024
165b11a
Merge branch 'main' into features/incremental-source-generator
luizfbicalho Jun 21, 2024
b7146fe
Improve exception message formatting
luizfbicalho Jun 21, 2024
dabde3c
Merge branch 'main' into features/incremental-source-generator
luizfbicalho Jun 26, 2024
e73abff
Merge branch 'main' into features/incremental-source-generator
luizfbicalho Jun 26, 2024
6e733b6
Merge branch 'main' into features/incremental-source-generator
luizfbicalho Jun 28, 2024
61dfbc0
Merge branch 'main' into features/incremental-source-generator
luizfbicalho Jul 1, 2024
d8b29ec
Merge branch 'main' into features/incremental-source-generator
rockfordlhotka Jul 3, 2024
f5d4135
Merge branch 'main' into features/incremental-source-generator
luizfbicalho Jul 5, 2024
70b2b3c
Add guide for CSLA.NET source generator
luizfbicalho Jul 6, 2024
db7ce4b
Merge branch 'features/incremental-source-generator' of https://githu…
luizfbicalho Jul 6, 2024
b49a45d
Merge branch 'main' into features/incremental-source-generator
luizfbicalho Jul 6, 2024
bbf8670
Merge branch 'MarimerLLC:main' into features/incremental-source-gener…
luizfbicalho Jul 6, 2024
30e9a7c
Update project configuration and metadata
luizfbicalho Jul 6, 2024
7b99d12
Restore AutoSerializableAttribute.cs content
luizfbicalho Jul 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
// <summary>Class that can be used for testing serialization behaviour</summary>
//-----------------------------------------------------------------------
using Csla.Serialization;
using System;
using System.Collections.Generic;
using System.Text;

namespace Csla.Generators.CSharp.TestObjects
{
Expand All @@ -17,15 +14,15 @@ namespace Csla.Generators.CSharp.TestObjects
public partial class AddressPOCO
{

public string AddressLine1 { get; set; }
public string? AddressLine1 { get; set; }

public string AddressLine2 { get; set; }
public string AddressLine2 { get; set; } = string.Empty;

public string Town { get; set; }
public string Town { get; set; } = string.Empty;

public string County { get; set; }
public string County { get; set; } = string.Empty;

public string Postcode { get; set; }
public string Postcode { get; set; } = string.Empty;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
// <summary>Class that can be used for testing serialization behaviour for classes implementing IMobileObject</summary>
//-----------------------------------------------------------------------
using Csla.Serialization.Mobile;
using System;
using System.Collections.Generic;
using System.Text;

namespace Csla.Generators.CSharp.TestObjects
{
Expand All @@ -20,7 +17,7 @@ namespace Csla.Generators.CSharp.TestObjects
public class EmailAddress : IMobileObject
{

public string Email { get; set; }
public string Email { get; set; } = string.Empty;

public void GetChildren(SerializationInfo info, MobileFormatter formatter)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
// <summary>Class that can be used for testing serialization behaviour related to nested classes</summary>
//-----------------------------------------------------------------------
using Csla.Serialization;
using System;
using System.Collections.Generic;

namespace Csla.Generators.CSharp.TestObjects
{
Expand All @@ -27,7 +25,7 @@ public partial class NestingPOCO
protected internal partial class NestedPOCO
{

public string Value { get; set; }
public string Value { get; set; } = string.Empty;

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
// <summary>Class that can be used for testing serialization behaviour related to naming clashes of nested classes</summary>
//-----------------------------------------------------------------------
using Csla.Serialization;
using System;
using System.Collections.Generic;
using System.Text;

namespace Csla.Generators.CSharp.TestObjects
{
Expand All @@ -29,7 +26,7 @@ public partial class NestingPOCO2
protected internal partial class NestedPOCO
{

public string Value { get; set; }
public string Value { get; set; } = string.Empty;

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
// </copyright>
// <summary>Complex class that can be used for testing serialization behaviour</summary>
//-----------------------------------------------------------------------
using System;
using Csla.Serialization;

namespace Csla.Generators.CSharp.TestObjects
Expand All @@ -23,14 +22,14 @@ public partial class PersonPOCO
#pragma warning disable CS0414 // Remove unused private members
private string _fieldTest = "foo";
#pragma warning restore CS0414 // Remove unused private members
private string _lastName;
private string _lastName = string.Empty;

[AutoSerialized]
private string _middleName;
private string _middleName = string.Empty;

public int PersonId { get; set; }

public string FirstName { get; set; }
public string FirstName { get; set; } = string.Empty;

public string MiddleName => _middleName;

Expand Down Expand Up @@ -85,9 +84,9 @@ public DateTime GetDateOfBirth()
return DateOfBirth;
}

public AddressPOCO Address { get; set; }
public AddressPOCO? Address { get; set; }

public EmailAddress EmailAddress { get; set; }
public EmailAddress? EmailAddress { get; set; }

}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net6.0</TargetFrameworks>
<TargetFrameworks>net8.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<IsPackable>false</IsPackable>
</PropertyGroup>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,19 @@ namespace Csla.Generators.CSharp.AutoSerialization.Discovery
internal class DefinitionExtractionContext
{

private readonly GeneratorSyntaxContext _context;
private readonly SemanticModel _semanticModel;
private const string _serializationNamespace = "Csla.Serialization";
private const string _autoSerializableAttributeName = "AutoSerializableAttribute";
private const string _autoSerializedAttributeName = "AutoSerializedAttribute";
private const string _autoNonSerializedAttributeName = "AutoNonSerializedAttribute";
private const string _iMobileObjectInterfaceNamespace = "Csla.Serialization.Mobile";
private const string _iMobileObjectInterfaceName = "IMobileObject";

public DefinitionExtractionContext(GeneratorSyntaxContext context)
public DefinitionExtractionContext(SemanticModel semanticModel)
{
_context = context;
_semanticModel = semanticModel;
}

public GeneratorSyntaxContext Context => _context;

/// <summary>
/// Get the namespace of the type represented by a type declaration
Expand All @@ -41,7 +40,7 @@ public string GetTypeNamespace(TypeDeclarationSyntax typeDeclarationSyntax)
{
INamedTypeSymbol typeSymbol;

typeSymbol = _context.SemanticModel.GetDeclaredSymbol(typeDeclarationSyntax) as INamedTypeSymbol;
typeSymbol = _semanticModel.GetDeclaredSymbol(typeDeclarationSyntax) as INamedTypeSymbol;
if (typeSymbol is null || typeSymbol.ContainingNamespace is null) return string.Empty;
return typeSymbol.ContainingNamespace.ToString();
}
Expand All @@ -55,7 +54,7 @@ public string GetTypeNamespace(TypeSyntax typeSyntax)
{
INamedTypeSymbol typeSymbol;

typeSymbol = _context.SemanticModel.GetSymbolInfo(typeSyntax).Symbol as INamedTypeSymbol;
typeSymbol = _semanticModel.GetSymbolInfo(typeSyntax).Symbol as INamedTypeSymbol;
if (typeSymbol is null || typeSymbol.ContainingNamespace is null) return string.Empty;
return typeSymbol.ContainingNamespace.ToString();
}
Expand All @@ -69,7 +68,7 @@ public bool IsTypeAutoSerializable(TypeDeclarationSyntax typeDeclarationSyntax)
{
INamedTypeSymbol typeSymbol;

typeSymbol = _context.SemanticModel.GetDeclaredSymbol(typeDeclarationSyntax) as INamedTypeSymbol;
typeSymbol = _semanticModel.GetDeclaredSymbol(typeDeclarationSyntax) as INamedTypeSymbol;
return IsTypeDecoratedBy(typeSymbol, _autoSerializableAttributeName, _serializationNamespace);
}

Expand All @@ -82,7 +81,7 @@ public bool IsTypeAutoSerializable(TypeSyntax typeSyntax)
{
INamedTypeSymbol typeSymbol;

typeSymbol = _context.SemanticModel.GetSymbolInfo(typeSyntax).Symbol as INamedTypeSymbol;
typeSymbol = _semanticModel.GetSymbolInfo(typeSyntax).Symbol as INamedTypeSymbol;
if (typeSymbol is null) return false;
return IsTypeDecoratedBy(typeSymbol, _autoSerializableAttributeName, _serializationNamespace);
}
Expand All @@ -97,7 +96,7 @@ public bool DoesTypeImplementIMobileObject(TypeSyntax typeSyntax)
{
INamedTypeSymbol typeSymbol;

typeSymbol = _context.SemanticModel.GetSymbolInfo(typeSyntax).Symbol as INamedTypeSymbol;
typeSymbol = _semanticModel.GetSymbolInfo(typeSyntax).Symbol as INamedTypeSymbol;
if (typeSymbol is null) return false;

foreach (ITypeSymbol interfaceSymbol in typeSymbol.AllInterfaces)
Expand Down Expand Up @@ -179,7 +178,7 @@ private bool IsPropertyDecoratedWith(PropertyDeclarationSyntax propertyDeclarati

foreach (AttributeSyntax attributeSyntax in propertyDeclaration.AttributeLists.SelectMany(al => al.Attributes))
{
appliedAttributeSymbol = _context.SemanticModel.GetTypeInfo(attributeSyntax).Type as INamedTypeSymbol;
appliedAttributeSymbol = _semanticModel.GetTypeInfo(attributeSyntax).Type as INamedTypeSymbol;
if (IsMatchingTypeSymbol(appliedAttributeSymbol, desiredAttributeTypeName, desiredAttributeTypeNamespace))
{
return true;
Expand All @@ -201,7 +200,7 @@ private bool IsFieldDecoratedWith(FieldDeclarationSyntax fieldDeclaration, strin

foreach (AttributeSyntax attributeSyntax in fieldDeclaration.AttributeLists.SelectMany(al => al.Attributes))
{
appliedAttributeSymbol = _context.SemanticModel.GetTypeInfo(attributeSyntax).Type as INamedTypeSymbol;
appliedAttributeSymbol = _semanticModel.GetTypeInfo(attributeSyntax).Type as INamedTypeSymbol;
if (IsMatchingTypeSymbol(appliedAttributeSymbol, desiredAttributeTypeName, desiredAttributeTypeNamespace))
{
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,24 @@ public static ExtractedFieldDefinition ExtractFieldDefinition(DefinitionExtracti
fieldDefinition.TypeDefinition.TypeNamespace = extractionContext.GetTypeNamespace(fieldDeclaration.Declaration.Type);
fieldDefinition.TypeDefinition.IsAutoSerializable = extractionContext.IsTypeAutoSerializable(fieldDeclaration.Declaration.Type);
fieldDefinition.TypeDefinition.ImplementsIMobileObject = extractionContext.DoesTypeImplementIMobileObject(fieldDeclaration.Declaration.Type);

fieldDefinition.TypeDefinition.Nullable = GetFieldTypeNullable(extractionContext, fieldDeclaration);
return fieldDefinition;
}

#region Private Helper Methods

/// <summary>
/// Determines whether the field type is nullable.
/// </summary>
/// <param name="extractionContext">The definition extraction context in which the extraction is being performed.</param>
/// <param name="fieldDeclaration">The FieldDeclarationSyntax representing the field declaration.</param>
/// <returns><c>true</c> if the field type is nullable; otherwise, <c>false</c>.</returns>
private static bool GetFieldTypeNullable(DefinitionExtractionContext extractionContext, FieldDeclarationSyntax fieldDeclaration)
{
return fieldDeclaration.Declaration.Type is NullableTypeSyntax nullableType;
}


/// <summary>
/// Extract the name of the field for which we are building information
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,22 @@ public static ExtractedPropertyDefinition ExtractPropertyDefinition(DefinitionEx
propertyDefinition.TypeDefinition.TypeNamespace = extractionContext.GetTypeNamespace(propertyDeclaration.Type);
propertyDefinition.TypeDefinition.IsAutoSerializable = extractionContext.IsTypeAutoSerializable(propertyDeclaration.Type);
propertyDefinition.TypeDefinition.ImplementsIMobileObject = extractionContext.DoesTypeImplementIMobileObject(propertyDeclaration.Type);
propertyDefinition.TypeDefinition.Nullable = GetFieldTypeNullable(extractionContext, propertyDeclaration);

return propertyDefinition;
}

#region Private Helper Methods
/// <summary>
/// Determines whether the field type is nullable.
/// </summary>
/// <param name="extractionContext">The definition extraction context in which the extraction is being performed.</param>
/// <param name="propertyDeclaration">The PropertyDeclarationSyntax representing the field declaration.</param>
/// <returns><c>true</c> if the field type is nullable; otherwise, <c>false</c>.</returns>
private static bool GetFieldTypeNullable(DefinitionExtractionContext extractionContext, PropertyDeclarationSyntax propertyDeclaration)
{
return propertyDeclaration.Type is NullableTypeSyntax nullableType;
}

/// <summary>
/// Extract the name of the property for which we are building information
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class ExtractedMemberTypeDefinition
public string TypeNamespace { get; set; }

/// <summary>
/// Whether the type is marked as AutoSerialiable
/// Whether the type is marked as AutoSerializable
/// </summary>
public bool IsAutoSerializable { get; set; } = false;

Expand All @@ -35,5 +35,9 @@ public class ExtractedMemberTypeDefinition
/// </summary>
public bool ImplementsIMobileObject { get; set; } = false;

/// <summary>
/// Gets or sets a value indicating whether the type is nullable.
/// </summary>
public bool Nullable { get; internal set; }
}
}
luizfbicalho marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Text;
using Csla.Generators.CSharp.AutoSerialization.Discovery;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace Csla.Generators.CSharp.AutoSerialization
{
[Generator]
public class IncrementalSerializationPartialsGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Register a syntax receiver to collect information during the initial parsing phase
var classDeclarations = context.SyntaxProvider.ForAttributeWithMetadataName(
"Csla.Serialization.AutoSerializableAttribute",

predicate: static (s, _) => (s is ClassDeclarationSyntax || s is StructDeclarationSyntax) && s is TypeDeclarationSyntax type && type.Modifiers.Any(SyntaxKind.PartialKeyword),
transform: static (ctx, _) =>
{
if (ctx.TargetNode is TypeDeclarationSyntax typeDeclarationSyntax)
{
return TypeDefinitionExtractor.ExtractTypeDefinition(new DefinitionExtractionContext(ctx.SemanticModel), typeDeclarationSyntax);
}
return null;
})
.Where(static m => m is not null);

var nullable = context.CompilationProvider.Select((c, _) => c.Options.NullableContextOptions.AnnotationsEnabled());
// Combine the collected syntax nodes with the semantic model
var compilationAndClasses = nullable.Combine(classDeclarations.Collect());

// Set up the generation phase
context.RegisterSourceOutput(compilationAndClasses, static (spc, source) =>
{
var (nullable, classes) = source;
SerializationPartialBuilder builder = new SerializationPartialBuilder(nullable);
foreach (var typeDefinition in classes)
{
// Build the text for the generated type using the builder
var generationResults = builder.BuildPartialTypeDefinition(typeDefinition);

// Add the generated source to the output
spc.AddSource($"{generationResults.FullyQualifiedName}.g.cs",
SourceText.From(generationResults.GeneratedSource, Encoding.UTF8));
}
});
}
}
}
Loading
Loading