Как написать генератор кода который сможет поддерживать даже твоя бабушка?

—

от автора

Недавно мне понадобилось написать генератор кода для одного из своих проектов. Так как надо было обеспечить поддержку 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.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
А как вы генерируете код?
46.67% Использую StringBuilder 7
13.33% Использую Raw String Literal 2
40% Использую Roslyn Compiler API 6
33.33% Не генерирую код 5
Проголосовали 15 пользователей. Воздержались 3 пользователя.

ссылка на оригинал статьи https://habr.com/ru/articles/739364/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *