AArnott / CodeGeneration.Roslyn

Assists in performing Roslyn-based code generation during a build.
Microsoft Public License
408 stars 59 forks source link

Enhance Walkthrough Troubleshooting Tipps #186

Open ZeeOcho opened 4 years ago

ZeeOcho commented 4 years ago

I would like to propose to add some hints to the walkthrough that may make setup and troubleshooting easier. They are the result of me implementing my first code generator, so this can be considered user feedback :). I should note that I did not have any previous experience in Roslyn based code generation, so some of this might be obvious for people that used it before.

Following the example Code Generator was quite easy for me (I needed to do quite some updating of VS and Windows, but I got it running quite straight forward). I had some errors in my own implementation of my code generator and wasn't quite sure how to troubleshoot them. The following bits of information would have probably helped me with that.

  1. Each source file containing code generation attributes will result in corresponding .generated.cs - files in the /obj/ - Directory of the project. They contain the code generated from your generator.

  2. If your code generator throws an exception in the ICodeGenerator.GenerateAsync or IRichCodeGenerator.GenerateRichAsync, it will result in 2 errors in the build process. One is containing the type and text of the exception, the other will say (somewhat obscure) "MSB3073 The command "dotnet codegen "@obj\Debug\netstandard2.0\CodeGenTest1.csproj.dotnet-codegen.rsp"" exited with code 3 beendet." (translated from german, so the text might not be accurate. image

  3. You can output messages to the build log:

3.1 You can output messages to the build log by using the IProgress<Diagnostic> progress parameter, like this:

progress.Report(Diagnostic.Create(
    new DiagnosticDescriptor("CA1001", "Cannot apply attribute to non-partial class", "Make class partial", "Microsoft.Design", DiagnosticSeverity.Error, true)
    , Location.Create(applyToClass.SyntaxTree, applyToClass.Span)));

3.2 Or use the CodeGeneration.Roslyn.Logger extension methods like this:

Logger.Error("This message comes from Logger extension", "SOMECODE");

Giving you the following build log in VS: image

  1. If you Console.WriteLine in your code generator, it will show up in your build output window in VS.
amis92 commented 4 years ago

Thanks! Those are valuable insights, it's so nice of you to share them! I'll attempt to incorporate them when time allows.

daiplusplus commented 4 years ago

Another trick I'm using is to invoke System.Diagnostics.Debugger.Launch() inside my ICodeGenerator implementation's constructor - thus whenever I build my project, I get a debugger popup which then lets me set breakpoints in the GenerateAsync method.

This can probably be improved by only calling Launch() if a certain environment variable is set or .csproj property set.

public class DuplicateWithSuffixGenerator : ICodeGenerator
{
    private readonly string suffix;

    public DuplicateWithSuffixGenerator( AttributeData attributeData )
    {
        this.suffix = (string)attributeData.ConstructorArguments[0].Value;

        System.Diagnostics.Debugger.Launch();
        while( !System.Diagnostics.Debugger.IsAttached )
        {
            Thread.Sleep( 500 ); // eww, eww, eww
        }
    }

    public Task<SyntaxList<MemberDeclarationSyntax>> GenerateAsync( TransformationContext context, IProgress<Diagnostic> progress, CancellationToken cancellationToken )
    {
        // Our generator is applied to any class that our attribute is applied to.
        ClassDeclarationSyntax applyToClass = (ClassDeclarationSyntax)context.ProcessingNode;

        // Apply a suffix to the name of a copy of the class.
        ClassDeclarationSyntax copy = applyToClass.WithIdentifier(SyntaxFactory.Identifier(applyToClass.Identifier.ValueText + this.suffix));

        // Return our modified copy. It will be added to the user's project for compilation.
        SyntaxList<MemberDeclarationSyntax> results = SyntaxFactory.SingletonList<MemberDeclarationSyntax>(copy);

        return Task.FromResult( results );
    }
}