Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -581,6 +581,12 @@ private static void BuildPrivateSetMethod(StringBuilder output, string className
var classSymbols = new List<(ClassSymbol ClassSymbol, FluentData FluentData)>();
foreach (var fluentDataItem in _items)
{
// Skip invalid/default FluentData items (e.g. from failed transforms)
if (string.IsNullOrEmpty(fluentDataItem.MetadataName))
{
continue;
}

if (_compilationHelper.TryGetNamedTypeSymbolByFullMetadataName(fluentDataItem, out var classSymbol))
{
classSymbols.Add((classSymbol, fluentDataItem));
Expand Down
54 changes: 26 additions & 28 deletions src/FluentBuilderGenerator/FluentBuilderSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,29 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
}
});

var diagnostics = new List<Diagnostic>();

var fluentBuilderClassesProvider = context.SyntaxProvider
.CreateSyntaxProvider(
(syntaxNode, ct) => ShouldHandle(syntaxNode, ct, diagnostics),
(generatorSyntaxContext, ct) => Transform(generatorSyntaxContext, ct, diagnostics)
)
static (syntaxNode, _) => AutoGenerateBuilderSyntaxReceiver.IsSyntaxTarget(syntaxNode),
static (generatorSyntaxContext, _) => Transform(generatorSyntaxContext)
);

// Report diagnostics from the predicate/transform phase
var diagnosticsProvider = fluentBuilderClassesProvider
.Where(static x => x.Diagnostic != null)
.Select(static (x, _) => x.Diagnostic!);

context.RegisterSourceOutput(diagnosticsProvider, static (sourceProductionContext, diagnostic) =>
{
sourceProductionContext.ReportDiagnostic(diagnostic);
});

// Filter out invalid items and collect valid FluentData
var validItemsProvider = fluentBuilderClassesProvider
.Where(static x => x.Diagnostic == null && !string.IsNullOrEmpty(x.Data.MetadataName))
.Select(static (x, _) => x.Data)
.Collect();
var combined2 = fluentBuilderClassesProvider.Combine(combinedProvider).Select((x, _) => new

var combined2 = validItemsProvider.Combine(combinedProvider).Select((x, _) => new
{
Items = x.Left,
LanguageData = x.Right.Left,
Expand All @@ -89,11 +102,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context)

context.RegisterSourceOutput(combined2, (sourceProductionContext, data) =>
{
foreach (var diagnostic in diagnostics)
{
sourceProductionContext.ReportDiagnostic(diagnostic);
}

var generator = new FluentBuilderClassesGenerator(data.Items, data.CompilationHelper, data.LanguageData.SupportsNullable);

try
Expand All @@ -113,25 +121,15 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
});
}

private static bool ShouldHandle(SyntaxNode syntaxNode, CancellationToken _, List<Diagnostic> diagnostics)
{
var result = AutoGenerateBuilderSyntaxReceiver.CheckSyntaxNode(syntaxNode, out var diagnostic);
if (diagnostic != null)
{
diagnostics.Add(diagnostic);
}

return result;
}

private static FluentData Transform(GeneratorSyntaxContext gsc, CancellationToken _, List<Diagnostic> diagnostics)
private static (FluentData Data, Diagnostic? Diagnostic) Transform(GeneratorSyntaxContext gsc)
{
var result = AutoGenerateBuilderSyntaxReceiver.HandleSyntaxNode(gsc.Node, gsc.SemanticModel, out var diagnostic);
if (diagnostic != null)
// Do the full validation check (including modifier check) in the transform
if (!AutoGenerateBuilderSyntaxReceiver.CheckSyntaxNode(gsc.Node, out var checkDiagnostic))
{
diagnostics.Add(diagnostic);
return (default, checkDiagnostic);
}

return result;
var data = AutoGenerateBuilderSyntaxReceiver.HandleSyntaxNode(gsc.Node, gsc.SemanticModel, out var diagnostic);
return (data, diagnostic);
}
}
49 changes: 47 additions & 2 deletions src/FluentBuilderGenerator/Models/FluentData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace FluentBuilderGenerator.Models;

internal struct FluentData
internal struct FluentData : IEquatable<FluentData>
{
public string Namespace { get; init; }

Expand All @@ -26,5 +26,50 @@ internal struct FluentData

public BuilderType BuilderType { get; init; }

public FluentBuilderMethods Methods { get; init; }
public FluentBuilderMethods Methods { get; init; }

public bool Equals(FluentData other)
{
return Namespace == other.Namespace &&
ClassModifier == other.ClassModifier &&
ShortBuilderClassName == other.ShortBuilderClassName &&
FullBuilderClassName == other.FullBuilderClassName &&
FullRawTypeName == other.FullRawTypeName &&
ShortTypeName == other.ShortTypeName &&
MetadataName == other.MetadataName &&
HandleBaseClasses == other.HandleBaseClasses &&
Accessibility == other.Accessibility &&
BuilderType == other.BuilderType &&
Methods == other.Methods &&
(Usings == other.Usings || (Usings != null && other.Usings != null && Usings.SequenceEqual(other.Usings)));
}

public override bool Equals(object? obj) => obj is FluentData other && Equals(other);

public override int GetHashCode()
{
unchecked
{
var hash = 17;
hash = hash * 31 + (Namespace?.GetHashCode() ?? 0);
hash = hash * 31 + (ClassModifier?.GetHashCode() ?? 0);
hash = hash * 31 + (ShortBuilderClassName?.GetHashCode() ?? 0);
hash = hash * 31 + (FullBuilderClassName?.GetHashCode() ?? 0);
hash = hash * 31 + (FullRawTypeName?.GetHashCode() ?? 0);
hash = hash * 31 + (ShortTypeName?.GetHashCode() ?? 0);
hash = hash * 31 + (MetadataName?.GetHashCode() ?? 0);
hash = hash * 31 + HandleBaseClasses.GetHashCode();
hash = hash * 31 + Accessibility.GetHashCode();
hash = hash * 31 + BuilderType.GetHashCode();
hash = hash * 31 + Methods.GetHashCode();
if (Usings != null)
{
foreach (var u in Usings)
{
hash = hash * 31 + (u?.GetHashCode() ?? 0);
}
}
return hash;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,29 @@ internal static class AutoGenerateBuilderSyntaxReceiver
private const string ModifierPublic = "public";
private const string ModifierInternal = "internal";

/// <summary>
/// A fast syntax-only check to determine if a node is a candidate for processing.
/// This is used as the predicate in the incremental generator pipeline.
/// </summary>
public static bool IsSyntaxTarget(SyntaxNode syntaxNode)
{
TypeDeclarationSyntax typeDeclarationSyntax;
if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax)
{
typeDeclarationSyntax = classDeclarationSyntax;
}
else if (syntaxNode is RecordDeclarationSyntax recordDeclarationSyntax)
{
typeDeclarationSyntax = recordDeclarationSyntax;
}
else
{
return false;
}

return typeDeclarationSyntax.AttributeLists.Any(x => x.Attributes.Any(AttributeArgumentListParser.IsMatch));
}

public static bool CheckSyntaxNode(SyntaxNode syntaxNode, out Diagnostic? diagnostic)
{
TypeDeclarationSyntax typeDeclarationSyntax;
Expand Down Expand Up @@ -53,12 +76,20 @@ public static bool CheckSyntaxNode(SyntaxNode syntaxNode, out Diagnostic? diagno

public static FluentData HandleSyntaxNode(SyntaxNode syntaxNode, SemanticModel semanticModel, out Diagnostic? diagnostic)
{
return syntaxNode switch
if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax)
{
ClassDeclarationSyntax classDeclarationSyntax when TryGet(classDeclarationSyntax, semanticModel, out var data, out diagnostic) => data,
RecordDeclarationSyntax recordDeclarationSyntax when TryGet(recordDeclarationSyntax, semanticModel, out var data, out diagnostic) => data,
_ => throw new InvalidOperationException("Only classes or records are supported."),
};
TryGet(classDeclarationSyntax, semanticModel, out var data, out diagnostic);
return data;
}

if (syntaxNode is RecordDeclarationSyntax recordDeclarationSyntax)
{
TryGet(recordDeclarationSyntax, semanticModel, out var data, out diagnostic);
return data;
}

diagnostic = null;
return default;
}

private static bool TryGet(TypeDeclarationSyntax typeDeclarationSyntax, SemanticModel semanticModel, out FluentData data, out Diagnostic? diagnostic)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by https://github.com/StefH/FluentBuilder version 0.15.0.0
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using FluentBuilderGeneratorTests.FluentBuilder;
using AbcTest.OtherNamespace;

namespace AbcTest.OtherNamespace
{
public static partial class ClassOnOtherNamespaceBuilderExtensions
{
public static ClassOnOtherNamespaceBuilder AsBuilder(this AbcTest.OtherNamespace.ClassOnOtherNamespace instance)
{
return new ClassOnOtherNamespaceBuilder().UsingInstance(instance);
}
}

public partial class ClassOnOtherNamespaceBuilder : Builder<AbcTest.OtherNamespace.ClassOnOtherNamespace>
{
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by https://github.com/StefH/FluentBuilder version 0.15.0.0
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using FluentBuilderGeneratorTests.FluentBuilder;
using AbcTest.OtherNamespace;
namespace AbcTest.OtherNamespace
{
public static partial class ClassOnOtherNamespaceBuilderExtensions
{
public static ClassOnOtherNamespaceBuilder AsBuilder(this AbcTest.OtherNamespace.ClassOnOtherNamespace instance)
{
return new ClassOnOtherNamespaceBuilder().UsingInstance(instance);
}
}
public partial class ClassOnOtherNamespaceBuilder : Builder<AbcTest.OtherNamespace.ClassOnOtherNamespace>
{
private bool _idIsSet;
private Lazy<int> _id = new Lazy<int>(() => default(int));
public ClassOnOtherNamespaceBuilder WithId(int value) => WithId(() => value);
Expand All @@ -36,7 +36,7 @@ public ClassOnOtherNamespaceBuilder WithId(Func<int> func)
_idIsSet = true;
return this;
}

private bool _Constructor1204632294_IsSet;
private Lazy<AbcTest.OtherNamespace.ClassOnOtherNamespace> _Constructor1204632294 = new Lazy<AbcTest.OtherNamespace.ClassOnOtherNamespace>(() => new AbcTest.OtherNamespace.ClassOnOtherNamespace()
{
Expand All @@ -59,7 +59,7 @@ public ClassOnOtherNamespaceBuilder UsingConstructor()
return this;
}


public ClassOnOtherNamespaceBuilder UsingInstance(ClassOnOtherNamespace value) => UsingInstance(() => value);

public ClassOnOtherNamespaceBuilder UsingInstance(Func<ClassOnOtherNamespace> func)
Expand Down Expand Up @@ -105,7 +105,7 @@ public override ClassOnOtherNamespace Build(bool useObjectInitializer)
{

};

}
}
}
}
#nullable disable
Loading