Skip to content

Commit

Permalink
Move to Single File Emission
Browse files Browse the repository at this point in the history
  • Loading branch information
justindbaur committed Dec 4, 2023
1 parent 3485768 commit 6381c1b
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 133 deletions.
72 changes: 70 additions & 2 deletions src/Pretender.SourceGenerator/Emitter/GrandEmitter.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,88 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace Pretender.SourceGenerator.Emitter
{
internal class GrandEmitter
{
private readonly ImmutableArray<PretendEmitter> _pretendEmitters;
private readonly ImmutableArray<SetupEmitter> _setupEmitters;
private readonly ImmutableArray<VerifyEmitter> _verifyEmitters;
private readonly ImmutableArray<CreateEmitter> _createEmitters;

public GrandEmitter(ImmutableArray<PretendEmitter> pretendEmitters)
public GrandEmitter(
ImmutableArray<PretendEmitter> pretendEmitters,
ImmutableArray<SetupEmitter> setupEmitters,
ImmutableArray<VerifyEmitter> verifyEmitters,
ImmutableArray<CreateEmitter> createEmitters)
{
_pretendEmitters = pretendEmitters;
_setupEmitters = setupEmitters;
_verifyEmitters = verifyEmitters;
_createEmitters = createEmitters;
}

public CompilationUnitSyntax Emit(CancellationToken cancellationToken)
{
throw new NotImplementedException();
var namespaceDeclaration = KnownBlocks.OurNamespace
.AddUsings(
UsingDirective(ParseName("System")),
KnownBlocks.CompilerServicesUsing,
UsingDirective(ParseName("System.Threading.Tasks")),
KnownBlocks.PretenderUsing,
KnownBlocks.PretenderInternalsUsing
);

foreach (var pretendEmitter in _pretendEmitters)
{
namespaceDeclaration = namespaceDeclaration
.AddMembers(pretendEmitter.Emit(cancellationToken));
}

var setupInterceptorsClass = ClassDeclaration("SetupInterceptors")
.WithModifiers(TokenList(Token(SyntaxKind.FileKeyword), Token(SyntaxKind.StaticKeyword)));

int setupIndex = 0;
foreach (var setupEmitter in _setupEmitters)
{
setupInterceptorsClass = setupInterceptorsClass
.AddMembers(setupEmitter.Emit(setupIndex, cancellationToken));
setupIndex++;
}

var verifyInterceptorsClass = ClassDeclaration("VerifyInterceptors")
.AddModifiers(Token(SyntaxKind.FileKeyword), Token(SyntaxKind.StaticKeyword));

int verifyIndex = 0;
foreach (var verifyEmitter in _verifyEmitters)
{
verifyInterceptorsClass = verifyInterceptorsClass
.AddMembers(verifyEmitter.Emit(verifyIndex, cancellationToken));
verifyIndex++;
}

var createInterceptorsClass = ClassDeclaration("CreateInterceptors")
.AddModifiers(Token(SyntaxKind.FileKeyword), Token(SyntaxKind.StaticKeyword));

int createIndex = 0;
foreach (var createEmitter in _createEmitters)
{
createInterceptorsClass = createInterceptorsClass
.AddMembers(createEmitter.Emit(cancellationToken));
createIndex++;
}

namespaceDeclaration = namespaceDeclaration
.AddMembers(setupInterceptorsClass, verifyInterceptorsClass, createInterceptorsClass);

return CompilationUnit()
.AddMembers(
KnownBlocks.InterceptsLocationAttribute,
namespaceDeclaration)
.NormalizeWhitespace();
}
}
}
23 changes: 3 additions & 20 deletions src/Pretender.SourceGenerator/Emitter/PretendEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public PretendEmitter(ITypeSymbol pretendType, bool fillExisting)

public ITypeSymbol PretendType => _pretendType;

public CompilationUnitSyntax Emit(CancellationToken token)
public TypeDeclarationSyntax Emit(CancellationToken token)
{
var pretendFieldAssignment = ExpressionStatement(
AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
Expand Down Expand Up @@ -128,25 +128,8 @@ public CompilationUnitSyntax Emit(CancellationToken token)
// TODO: Add properties

// TODO: Generate debugger display
classDeclaration = classDeclaration
.WithModifiers(TokenList(Token(SyntaxKind.InternalKeyword)));

SyntaxTriviaList leadingTrivia = TriviaList(
Comment("// <auto-generated/>"),
Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true)),
Comment("/// <inheritdoc/>"));

var sourceGenerationNamespace = KnownBlocks.OurNamespace
.AddMembers(classDeclaration.WithInheritDoc())
.AddUsings(
UsingDirective(IdentifierName("System.Reflection")),
KnownBlocks.PretenderUsing
);

return CompilationUnit()
.AddMembers(sourceGenerationNamespace)
.WithLeadingTrivia(leadingTrivia)
.NormalizeWhitespace();
return classDeclaration
.WithModifiers(TokenList(Token(SyntaxKind.FileKeyword)));
}

private static FieldDeclarationSyntax CreateMethodInfoField(IMethodSymbol method, ExpressionSyntax expressionSyntax)
Expand Down
9 changes: 2 additions & 7 deletions src/Pretender.SourceGenerator/Emitter/SetupEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@ public SetupEmitter(SetupActionEmitter setupActionEmitter, IInvocationOperation
}

// TODO: Run cancellationToken a lot more
public MemberDeclarationSyntax[] Emit(int index, CancellationToken cancellationToken)
public MemberDeclarationSyntax Emit(int index, CancellationToken cancellationToken)
{
var setupMethod = _setupActionEmitter.SetupMethod;
var pretendType = _setupActionEmitter.PretendType;

var allMembers = new List<MemberDeclarationSyntax>();

var interceptsLocation = new InterceptsLocationInfo(_setupInvocation);

// TODO: This is wrong
Expand All @@ -37,7 +35,7 @@ public MemberDeclarationSyntax[] Emit(int index, CancellationToken cancellationT

var setupCreatorInvocation = _setupActionEmitter.CreateSetupGetter(cancellationToken);

var fullSetupMethod = MethodDeclaration(returnType, $"Setup{index}")
return MethodDeclaration(returnType, $"Setup{index}")
.WithBody(Block(ReturnStatement(setupCreatorInvocation)))
.WithParameterList(ParameterList(SeparatedList(new[]
{
Expand All @@ -51,9 +49,6 @@ public MemberDeclarationSyntax[] Emit(int index, CancellationToken cancellationT
.WithModifiers(TokenList(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword)))
.WithAttributeLists(SingletonList(AttributeList(
SingletonSeparatedList(interceptsLocation.ToAttributeSyntax()))));

allMembers.Add(fullSetupMethod);
return [.. allMembers];
}
}
}
120 changes: 18 additions & 102 deletions src/Pretender.SourceGenerator/PretenderSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
});

#region Pretend
IncrementalValuesProvider<(PretendEmitter? Emitter, ImmutableArray<Diagnostic>? Diagnostics)> pretendsWithDiagnostics =
IncrementalValuesProvider<(PretendEmitter? Emitter, ImmutableArray<Diagnostic>? Diagnostics)> pretendEmittersWithDiagnostics =
context.SyntaxProvider.CreateSyntaxProvider(
predicate: (node, _) => PretendInvocation.IsCandidateSyntaxNode(node),
transform: PretendInvocation.Create)
Expand All @@ -56,17 +56,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
})
.WithTrackingName("Pretend");

var pretends = ReportDiagnostics(context, pretendsWithDiagnostics);

context.RegisterSourceOutput(pretends, static (context, emitter) =>
{
var compilationUnit = emitter.Emit(context.CancellationToken);
context.AddSource($"Pretender.Type.{emitter.PretendType.ToPretendName()}.g.cs", compilationUnit.GetText(Encoding.UTF8));
});
var pretendEmitters = ReportDiagnostics(context, pretendEmittersWithDiagnostics);
#endregion

#region Setup
IncrementalValuesProvider<(SetupEmitter? Emitter, ImmutableArray<Diagnostic>? Diagnostics)> setups =
IncrementalValuesProvider<(SetupEmitter? Emitter, ImmutableArray<Diagnostic>? Diagnostics)> setupEmittersWithDiagnostics =
context.SyntaxProvider.CreateSyntaxProvider(
predicate: static (node, _) => SetupInvocation.IsCandidateSyntaxNode(node),
transform: SetupInvocation.Create)
Expand All @@ -80,56 +74,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
})
.WithTrackingName("Setup");

context.RegisterSourceOutput(setups.Collect(), static (context, setups) =>
{
var members = new List<MemberDeclarationSyntax>();
for (var i = 0; i < setups.Length; i++)
{
var setup = setups[i];

if (setup.Diagnostics is ImmutableArray<Diagnostic> diagnostics)
{
foreach (var diagnostic in diagnostics)
{
context.ReportDiagnostic(diagnostic);
}
}

if (setup.Emitter is SetupEmitter emitter)
{
members.AddRange(emitter.Emit(i, context.CancellationToken));
}
}

var classDeclaration = SyntaxFactory.ClassDeclaration("SetupInterceptors")
.WithModifiers(SyntaxFactory.TokenList(
SyntaxFactory.Token(SyntaxKind.FileKeyword),
SyntaxFactory.Token(SyntaxKind.StaticKeyword)))
.AddMembers([.. members]);

var namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName("Pretender.SourceGeneration"))
.AddMembers(classDeclaration)
.AddUsings(
SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System")),
KnownBlocks.CompilerServicesUsing,
SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Linq.Expressions")),
SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Threading.Tasks")),
KnownBlocks.PretenderUsing,
KnownBlocks.PretenderInternalsUsing
);

var il = KnownBlocks.InterceptsLocationAttribute;

var compilationUnit = SyntaxFactory.CompilationUnit()
.AddMembers(il, namespaceDeclaration)
.NormalizeWhitespace();

context.AddSource("Pretender.Setups.g.cs", compilationUnit.GetText(Encoding.UTF8));
});
var setups = ReportDiagnostics(context, setupEmittersWithDiagnostics);
#endregion

#region Verify
IncrementalValuesProvider<(VerifyEmitter? Emitter, ImmutableArray<Diagnostic>? Diagnostics)> verifyCallsWithDiagnostics =
IncrementalValuesProvider<(VerifyEmitter? Emitter, ImmutableArray<Diagnostic>? Diagnostics)> verifyEmittersWithDiagnostics =
context.SyntaxProvider.CreateSyntaxProvider(
predicate: (node, _) => VerifyInvocation.IsCandidateSyntaxNode(node),
transform: VerifyInvocation.Create)
Expand All @@ -144,30 +93,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
})
.WithTrackingName("Verify");

var verifyEmitters = ReportDiagnostics(context, verifyCallsWithDiagnostics);

context.RegisterSourceOutput(verifyEmitters.Collect(), (context, inputs) =>
{
var methods = new List<MethodDeclarationSyntax>();
for (var i = 0; i < inputs.Length; i++)
{
var input = inputs[i];

var method = input.Emit(0, context.CancellationToken);
methods.Add(method);
}

if (methods.Count > 0)
{
// Emit all methods
var compilationUnit = CommonSyntax.CreateVerifyCompilationUnit([.. methods]);
context.AddSource("Pretender.Verifies.g.cs", compilationUnit.GetText(Encoding.UTF8));
}
});
var verifyEmitters = ReportDiagnostics(context, verifyEmittersWithDiagnostics);
#endregion

#region Create
var createCalls = context.SyntaxProvider.CreateSyntaxProvider(
var createEmittersWithDiagnostics = context.SyntaxProvider.CreateSyntaxProvider(
predicate: (node, _) => CreateInvocation.IsCandidateSyntaxNode(node),
transform: CreateInvocation.Create)
.Where(i => i is not null)!
Expand All @@ -180,36 +110,22 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
})
.WithTrackingName("Create");

var createEmitters = ReportDiagnostics(context, createCalls);
var createEmitters = ReportDiagnostics(context, createEmittersWithDiagnostics);
#endregion

context.RegisterSourceOutput(createEmitters, static (context, emitter) =>
context.RegisterSourceOutput(
pretendEmitters.Collect()
.Combine(setups.Collect())
.Combine(verifyEmitters.Collect())
.Combine(createEmitters.Collect()), (context, emitters) =>
{
// TODO: Don't actually need a list here
var members = new List<MemberDeclarationSyntax>();
var (((pretends, setups), verifies), creates) = emitters;
var grandEmitter = new GrandEmitter(pretends, setups, verifies, creates);

string? pretendName = null;
var compilationUnit = grandEmitter.Emit(context.CancellationToken);

pretendName ??= emitter.Operation.TargetMethod.ReturnType.ToPretendName();
members.Add(emitter.Emit(context.CancellationToken));

if (members.Any())
{
var createClass = SyntaxFactory.ClassDeclaration("CreateInterceptors")
.AddModifiers(SyntaxFactory.Token(SyntaxKind.FileKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword))
.WithMembers(SyntaxFactory.List(members));

var createNamespace = KnownBlocks.OurNamespace
.AddMembers(createClass)
.AddUsings(KnownBlocks.CompilerServicesUsing, KnownBlocks.PretenderUsing);

var cu = SyntaxFactory.CompilationUnit()
.AddMembers(KnownBlocks.InterceptsLocationAttribute, createNamespace)
.NormalizeWhitespace();

context.AddSource($"Pretender.Creates.{pretendName}.g.cs", cu.GetText(Encoding.UTF8));
}
context.AddSource("Pretender.g.cs", compilationUnit.GetText(Encoding.UTF8));
});
#endregion
}

private static IncrementalValuesProvider<T> ReportDiagnostics<T>(IncrementalGeneratorInitializationContext context, IncrementalValuesProvider<(T? Emitter, ImmutableArray<Diagnostic>? Diagnostics)> source)
Expand Down
4 changes: 2 additions & 2 deletions test/SourceGeneratorTests/MainTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ public TestClass()
}
""");

Assert.Equal(2, result.GeneratedSources.Length);
Assert.Equal(1, result.GeneratedSources.Length);

Check warning on line 48 in test/SourceGeneratorTests/MainTests.cs

View workflow job for this annotation

GitHub Actions / Build

Do not use Assert.Equal() to check for collection size. Use Assert.Single instead. (https://xunit.net/xunit.analyzers/rules/xUnit2013)

var text1 = result.GeneratedSources[0].SourceText.ToString();
var text2 = result.GeneratedSources[1].SourceText.ToString();
//var text2 = result.GeneratedSources[1].SourceText.ToString();
}


Expand Down

0 comments on commit 6381c1b

Please sign in to comment.