Недавно мне понадобилось написать генератор кода для одного из своих проектов. Так как надо было обеспечить поддержку Unity 2021, от более современного API — incremental generators пришлось отказаться сразу. Но пост не об этом, а о том, как повысить читаемость и поддерживаемость синтаксического дерева для генерации исходного кода.
Допустим нам надо сгенерировать следующий класс:
[MyTestAttribute] public class TestClass { public string Value { get; set; } = "default"; }
Синтаксическое дерево для такого простого класса будет выглядеть так:
ClassDeclaration("TestClass") .WithAttributeLists( SingletonList( AttributeList( SingletonSeparatedList( Attribute( IdentifierName("MyTestAttribute")))))) .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword))) .WithMembers( SingletonList<MemberDeclarationSyntax>( PropertyDeclaration(PredefinedType(Token(SyntaxKind.StringKeyword)), Identifier("Value")) .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword))) .WithAccessorList( AccessorList(List(new[] { AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)), AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) }))) .WithInitializer(EqualsValueClause( LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("default")))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))))
Выглядит устрашающе не правда ли? Конечно же, мало кто пишет это все вручную. Как правило, используют готовые инструменты для генерации, например, RoslynQuoter. Но читаемость и поддерживаемость такого кода оставляет желать лучшего.
К счастью, мы можем упростить данный код используя виджеты. Я не стал придумывать ничего нового, а просто применил свой опыт работы с Flutter и здесь.
Используя виджеты, код для генерации нашего класса будет выглядеть так:
ClassWidget( identifier: "TestClass", modifier: SyntaxKind.PublicKeyword, attribute: Attribute(IdentifierName("MyTestAttribute")), member: PropertyWidget( identifier: "Value", type: PredefinedType(Token(SyntaxKind.StringKeyword)), modifier: SyntaxKind.PublicKeyword, accessors: new[] { SyntaxKind.GetAccessorDeclaration, SyntaxKind.SetAccessorDeclaration }, initializer: StringLiteralExpressionWidget("default") ) )
Уверен, даже если вы никогда не сталкивались с Roslyn Compiler API, вы всё равно поймете, каким будет рузультат выполнения данного кода, и затратите на это куда меньше сил и времени, в отличии от стандартного подхода.
ClassWidget под капотом
private static ClassDeclarationSyntax ClassWidget( string identifier, SyntaxKind? modifier = null, IEnumerable<SyntaxKind>? modifiers = null, BaseTypeSyntax? baseType = null, IEnumerable<BaseTypeSyntax>? baseTypes = null, MemberDeclarationSyntax? member = null, IEnumerable<MemberDeclarationSyntax>? members = null, AttributeSyntax? attribute = null, IEnumerable<AttributeSyntax>? attributes = null, bool addGeneratedCodeAttributes = false) { var classDeclaration = ClassDeclaration(identifier); if (baseType is not null) { classDeclaration = classDeclaration .WithBaseList(BaseList(SingletonSeparatedList(baseType))); } if (baseTypes is not null) { classDeclaration = classDeclaration .WithBaseList(BaseList(SeparatedList(baseTypes))); } if (member is not null) { classDeclaration = classDeclaration .WithMembers(classDeclaration.Members.Add(member)); } if (members is not null) { classDeclaration = classDeclaration .WithMembers(List(members)); } return BaseWidgetDecoration( widget: classDeclaration, modifier: modifier, modifiers: modifiers, attribute: attribute, attributes: attributes, addGeneratedCodeAttributes: addGeneratedCodeAttributes); }
Код генератора и все виджеты можно найти на GitHub.
ссылка на оригинал статьи https://habr.com/ru/articles/739364/
Добавить комментарий