Как стать автором
Обновить

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

Время на прочтение3 мин
Количество просмотров4.1K

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

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
А как вы генерируете код?
38.46% Использую StringBuilder15
15.38% Использую Raw String Literal6
17.95% Использую Roslyn Compiler API7
48.72% Не генерирую код19
Проголосовали 39 пользователей. Воздержались 6 пользователей.
Теги:
Хабы:
Всего голосов 1: ↑1 и ↓0+1
Комментарии3

Публикации

Работа

Ближайшие события