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

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

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

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
А как вы генерируете код?
0% Использую StringBuilder 0
0% Использую Raw String Literal 0
0% Использую Roslyn Compiler API 0
0% Не генерирую код 0
Никто еще не голосовал. Воздержавшихся нет.
Источник: https://habr.com/ru/articles/739364/


Интересные статьи

Интересные статьи

В архитектуре кода иногда разделяют слой сущностей и слой моделей. В этой статье я расскажу о них и приведу два примера кода на языке Golang.
Привет, Хаброжители! Обычно на глубокое обучение смотрят с ужасом, считая, что только доктор математических наук или ботан, работающий в крутой айтишной корпорации, могут разобраться в этой теме. Отб...
Нравится Kotlin? Считаешь SQL мощным инструментом? Подташнивает от слов ORM, JPA, Hibernate? Есть выход! Автоматическая генерация SQL + JDBC без бойлер-плейта. Читать далее ...
Юникод исключительно сложен. Мало кто знает все хитрости: от невидимых символов и контрольных знаков до суррогатных пар и комбинированных эмодзи (когда при сложении двух знаков получается третий)...
Сегодня публикуем вторую часть перевода материала, посвящённого статическому анализу больших объёмов серверного Python-кода в Instagram. → Первая часть