⟨/⟩ 40_Arch/Tech/Angular/ADR_UI_002_NamingAndConventions.cs

195 строк · в начало

using System;
using Ban.Sdaid.Notation.Documents;
using Ban.Sdaid.Icd;

namespace Ban.Sdaid.Icd.Arch.Tech.Angular
{
    /// <summary>
    /// ADR-UI-002: Префикс, именование и базовые конвенции Angular-кода фронтенда ICD.
    /// Опирается на стиль Angular 22 (без суффиксов, signals, native control flow) и на
    /// переносимость собственного набора компонентов.
    /// </summary>
    public class ADR_UI_002_NamingAndConventions : IAdrDocument, IFolder<ArchAngularFolder>
    {
        public string Name => "ADR-UI-002. Префикс, именование и конвенции";

        public string Description =>
            @"Чтобы исключить коллизии селекторов с другими Angular-приложениями и обеспечить предсказуемый
стиль во всём проекте, нужен **свой префикс** и **единый набор базовых конвенций**. Правила
опираются на актуальный стиль Angular 22 и учитывают переносимость собственного набора компонентов.";

        public string Version => "1.1";
        public string Status => "accepted";
        public string[] Comments => new[]
        {
            "2026-08-11. Принято следом за ADR-UI-001 при настройке линтера фронтенда.",
            "2026-08-12. Уточнён строгий двухуровневый порядок членов класса (роль → видимость); " +
            "устранено противоречие «все поля вверху» с сортировкой по видимости.",
        };

        public Type? Supersedes => null;

        public static string S1_Context = """
            ## Контекст и постановка задачи

            Нужен свой префикс (чтобы селекторы и шаблонные идентификаторы не сталкивались с другими
            Angular-приложениями и заимствованным кодом) и единый набор конвенций — чтобы стиль был
            предсказуем, а будущая автоматизация (skills/codegen) опиралась на стабильные правила.

            Учитываем переносимость: собственный набор компонентов (`ui/`) должен выноситься в другой
            проект без переписывания. Отсюда — префикс держим там, где он реально нужен (DOM-селекторы), а
            имена TypeScript-классов оставляем нейтральными.
            """;

        public static string S2_DecisionDrivers = """
            ## Драйверы решения

            1. **Отсутствие коллизий** селекторов и шаблонных идентификаторов с другими приложениями.
            2. **Единообразие ревью** — стиль предсказуем, мелочи не обсуждаются в каждом PR.
            3. **Опора на нативные средства Angular 22** — signals, native control flow, `host` в
               декораторе, OnPush по умолчанию.
            4. **Переносимость набора** — вынос `ui/` в другой проект меняет минимум (префикс селекторов),
               не трогая имена классов.
            5. **Автоматизация** — генераторы компонентов/сервисов опираются на явные правила.
            """;

        public static string S3_ConsideredOptions = """
            ## Рассмотренные варианты

            - **A. Без префикса** — короче, но провоцирует коллизии селекторов с библиотеками и
              заимствованным кодом.
            - **B. Префикс `icd` в селекторах, классы без префикса** — короткое имя проекта как
              DOM-идентификатор; TypeScript-классы нейтральны (изоляция — модульной системой TS).
            - **C. Префикс и в классах** (`IcdButton…`) — избыточно для переносимого набора: при выносе
              пришлось бы переименовывать все классы.
            """;

        public static string S4_DecisionOutcome = """
            ## Решение

            Выбран **Вариант B**. Префикс `icd` — только в селекторах компонентов/директив и именах пайпов
            (шаблонные идентификаторы). Имена TypeScript-классов — чистый **PascalCase без префикса и без
            суффикса** (стиль Angular 22). При выносе `ui/` в библиотеку меняется только префикс селекторов
            (в `eslint.config.js` и шаблонах) — классы переносятся нетронутыми.

            ### Именование (стиль Angular 22 — без суффиксов)

            | Сущность | Класс | Селектор / имя | Файл |
            |---|---|---|---|
            | Компонент | `Button` | `icd-button` (element, kebab-case) | `button.ts` (+ `.html`, `.scss`) |
            | Директива | `Highlight` | `[icdHighlight]` (attribute, camelCase) | `highlight.ts` |
            | Pipe | `DateYmd` | `icdDateYmd` (в шаблоне) | `date-ymd.ts` |
            | Сервис | `AuthStore` | — | `auth-store.ts` |
            | Реестр маршрутов | `BaseRoutes` | — | `<feature>.routes.ts` |
            | Папка фичи | — | — | `modules/<feature-kebab>/` |

            Суффиксы `Component` / `Directive` / `Pipe` / `Service` **не используются** (актуальный стиль
            Angular). Файлы именуются по сущности (`button.ts`, не `button.component.ts`). Если имя сервиса
            коллизирует с типом/моделью — различаем **смысловым** суффиксом роли (`Store`, `Api`, `Client`),
            а не техническим `Service`.

            ### Разрешение коллизий имён (UI ↔ API)

            Имена UI-классов не префиксуются, поэтому теоретически возможно совпадение с типом api-модели
            (например, UI-контейнер `Card` и модель `Card`). На практике риск низкий: набор `ui/` — это
            generic-виджеты (`Button`, `Table`, `Input`), а api-модели ICD — доменные (`InformationCard`,
            `InputPacket`, `FundItem`), пересечений почти нет. Разрешение:

            - **По месту** — алиас импорта: `import { Card as CardModel } from '@api/…'` (стандартный
              механизм модулей TypeScript).
            - **Превентивно** — генерируемые api-модели несут суффикс (`Dto` / `Model`) либо импортируются
              через namespace (`import * as Api from '@api/…'`); тогда `CardDto` / `Api.Card` не конфликтуют
              с UI-классами в принципе.

            Префикс на имена UI-классов **не вводим** (переносимость набора важнее); коллизии закрываются на
            стороне api-моделей. Детали контракта api-моделей — в отдельном ADR.

            ### Базовые конвенции компонентов

            - **Standalone** (default в Angular 22 — `standalone: true` не указываем).
            - **Change detection** — **OnPush по умолчанию с Angular 22**, явно указывать не нужно; проект
              zoneless. `Default` указываем только там, где он осознанно требуется.
            - **`inject()`** вместо constructor-injection.
            - **`input()` / `output()`** функции вместо декораторов `@Input` / `@Output`.
            - **`computed()`** для производного состояния.
            - **`host`-объект в декораторе** вместо `@HostBinding` / `@HostListener`.
            - **Native control flow** `@if` / `@for` / `@switch`; не `*ngIf` / `*ngFor` / `*ngSwitch`.
            - **`class` / `style` bindings** вместо `ngClass` / `ngStyle`.
            - **`NgOptimizedImage`** для статических изображений.
            - Внешние шаблоны/стили — путь относительно `.ts`-файла компонента.

            ### Структура класса

            Порядок членов класса — **строгий, двухуровневый**:

            1. **По роли (внешняя ось):** поля → конструктор → методы. Все поля — вверху класса, до
               конструктора; методы — после него.
            2. **По области видимости (внутренняя ось, внутри полей и внутри методов):**
               `public` → `protected` → `private`.

            Итоговая раскладка сверху вниз:

            | № | Группа |
            |---|---|
            | 1 | `public`-поля |
            | 2 | `protected`-поля |
            | 3 | `private`-поля |
            | 4 | конструктор |
            | 5 | `public`-методы |
            | 6 | `protected`-методы |
            | 7 | `private`-методы |

            Уточнения: `static`-члены идут перед экземплярными в своей группе; геттеры/сеттеры трактуются как
            методы соответствующей видимости. Для Angular-компонентов это ложится естественно: `input()` /
            `output()` (`public`-поля) → производное состояние `computed()` (`protected`-поля) → внедрённые
            зависимости `inject()` (`private`-поля) → методы.

            ### Комментарии

            - JSDoc (`/** … */`) к методам с непустым именем намерения.
            - Не комментировать тривиальные геттеры/сеттеры и «что делает код» — комментарий нужен, когда
              не очевидно **почему**.

            ### TypeScript

            - `strict: true`; предпочитать вывод типов, когда он очевиден.
            - Избегать `any`; при неопределённости — `unknown`.
            - Не кастовать `as Type` / `<Type>`, если тип выводится; каст `<Type>{ … }` — только для
              осознанно частичного заполнения.
            """;

        public static string S5_PositiveConsequences = """
            ## Положительные следствия

            - Префикс в селекторах исключает коллизии в DOM; классы остаются переносимыми без переименования.
            - Стиль предсказуем — ревью о сути, а не о форматировании; генераторы опираются на явные правила.
            - Опора на нативный Angular 22 (OnPush по умолчанию, signals, control flow) — меньше кода и
              шаблонного шума.
            """;

        public static string S6_NegativeConsequences = """
            ## Отрицательные следствия и компромиссы

            - Префикс `icd-` в каждом селекторе — небольшой визуальный шум в шаблонах (узнаваемость важнее).
            - При выносе набора в библиотеку префикс селекторов всё же надо заменить — механическая работа,
              но локализованная (конфиг линтера + шаблоны).
            - Отказ от суффиксов требует дисциплины при коллизиях имён сервис/модель (решается ролью-суффиксом).
            """;

        public static string S7_Validation = """
            ## Проверка

            - Линтер `@angular-eslint` настроен (`eslint.config.js`): правила `component-selector` и
              `directive-selector` требуют префикс `icd`. `ng lint` проходит на текущем каркасе.
            - Любой новый компонент / директива / pipe соответствует таблице именования; спорные мелочи
              (порядок членов класса) не обсуждаются — есть однозначное правило.
            """;

        public static string S99_Related = $"""
            ## Связанные артефакты

            - {nameof(Ban.Sdaid.Icd.Arch.Tech.Angular.ADR_UI_001_Stack)} — Технологический стек фронтенда.
            """;
    }
}