Closed ZeeOcho closed 4 years ago
Did you declare the class as partial?
Did you declare the class as partial?
No I didn't. Thinking about your reply I think I didn't correctly understand how the CG process works. I thought I could "alter" the source code, but I have to think more in the lines of adding source files to the compilation unit, correct?
I'll try partial declaration. How would I go about multiple instances of my attribute on the same class, i.e. wanting to have multiple properties generated this way: would I have to generate a partial class for each property, or is there a way to "bundle" them?
Edit: I altered everything to partial stuff. Generated file looks good, but VS still "can't see" the generated property. Pasting code:
[GeneratePropertyForType(typeof(StringBuilder))]
public partial class GenTest1
{
public GenTest1()
{
//this.StringBuilder = new StringBuilder();
}
}
var applyToClass = context.ProcessingNode as ClassDeclarationSyntax;
if (!ClassIsPartial(applyToClass, progress))
return EmptyResult;
var property = SyntaxFactory.PropertyDeclaration(SyntaxFactory.ParseTypeName(TypeOfGeneratedProperty), TypeOfGeneratedProperty.Split('.').Last())
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
.AddAccessorListAccessors(SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)))
.AddAccessorListAccessors(SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)));
var partialClass = applyToClass.WithMembers(new SyntaxList<MemberDeclarationSyntax>().Add(property)).NormalizeWhitespace();
var result = new RichGenerationResult
{
Members = new SyntaxList<MemberDeclarationSyntax>().Add(partialClass)
};
return Task.FromResult(result);
// ------------------------------------------------------------------------------ //
// This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // // ------------------------------------------------------------------------------using CodeGenerator; using System; using System.Collections.Generic; using System.Text;
[GeneratePropertyForType(typeof(StringBuilder))] public partial class GenTest1 { public System.Text.StringBuilder StringBuilder { get; set; } }
Digging further, I am at least able to identify my problem. In my generated file (see above), there is no namespace, which most probably is the reason that the generated partial class is not found.
The generated file from the example contains the corresponding namespace.
The only difference is that I am using the IRichCodeGenerator interface. What do I have to do to make it add the namespace?
Well for your case it seems you really don't need any of the Rich additions, so I'd advise you to just use ICodeGenerator
instead.
Rich variant allows you to add using declarations, change namespaces etc. - but it's Result.Members
is essentially a list of root compilation unit members. So, for your specific instance, you'll need to either create the namespace syntax needed and add your class as a member of it, or reuse the Namespace syntax in which your context.ProcessingNode
is.
Thank you for the explanation. I read the documentation, but did not realize the effect of the differences. I switched back to ICodeGenerator
, and everything is working now. The only reason for me to try the IRichCodeGenerator
in the first place was because I missed making the class partial (result from incomplete understandig of how the code generation works), so I the rich generator was necessary when enhancing existing types. My proof of concept works now, thank you for your help! I intend to use it at work to simplify some IoC-Injection-Boilerplate-Code.
For the sake of completeness, here is my working code:
[CodeGenerationAttribute(typeof(PropertyGenerator))]
public class GeneratePropertyForTypeAttribute : Attribute
{
public Type PropertyType { get; }
public GeneratePropertyForTypeAttribute(Type propertyType)
{
PropertyType = propertyType;
}
}
public class PropertyGenerator : ICodeGenerator
{
private string TypeOfGeneratedProperty { get; }
public PropertyGenerator(AttributeData attributeData)
{
TypeOfGeneratedProperty = attributeData.ConstructorArguments[0].Value.ToString();
}
public Task<SyntaxList<MemberDeclarationSyntax>> GenerateAsync(TransformationContext context, IProgress<Diagnostic> progress, CancellationToken cancellationToken)
{
try
{
var applyToClass = context.ProcessingNode as ClassDeclarationSyntax;
if (!ClassIsPartial(applyToClass, progress))
return Task.FromResult(new SyntaxList<MemberDeclarationSyntax>());
var property = SyntaxFactory.PropertyDeclaration(SyntaxFactory.ParseTypeName(TypeOfGeneratedProperty), TypeOfGeneratedProperty.Split('.').Last())
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
.AddAccessorListAccessors(SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)))
.AddAccessorListAccessors(SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)));
var partialClass = applyToClass
.WithMembers(new SyntaxList<MemberDeclarationSyntax>().Add(property))
.WithAttributeLists(new SyntaxList<AttributeListSyntax>())
.WithModifiers(new SyntaxTokenList().Add(SyntaxFactory.Token(SyntaxKind.PartialKeyword)))
.NormalizeWhitespace();
var result = new SyntaxList<MemberDeclarationSyntax>().Add(partialClass);
return Task.FromResult(result);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
private bool ClassIsPartial(ClassDeclarationSyntax applyToClass, IProgress<Diagnostic> progress)
{
if (!applyToClass.Modifiers.Any(SyntaxKind.PartialKeyword))
{
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)));
return false;
}
return true;
}
}`
(I think closing this should be ok, since it is resolved).
Hi,
first of all: thank you for this project and the good work! I really like it.
I would like some help on how to add a property to a class, because I am not sure where I am going wrong. I followed the tutorial and got the DuplicateWithSuffixGenerator to work successfully in my project (it is a netstandard project).
What I want to do is the following:
This is the Attribute an IRichCodeGenerator definition I am using:
I know the generator is called because throwing an Exception in GenerateRichAsync shows up in build log, and the GenTest1...generated.cs file looks good:
However, if I try to uncomment the access to the StringBuilder-Property, I get an error saying it does not exist (in VS). What am I missing?
Thank you for your time and input!
Edit: Sorted out my Syntax game, code generation itself looks fine now.